Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
79f0044d
Commit
79f0044d
authored
Jan 29, 2020
by
Marvin Karegyeya
Committed by
Kushal Pandya
Jan 29, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor gl dropdown.js
parent
b4a2ba41
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
701 additions
and
695 deletions
+701
-695
app/assets/javascripts/gl_dropdown.js
app/assets/javascripts/gl_dropdown.js
+696
-695
changelogs/unreleased/Refactor-gl_dropdown-js.yml
changelogs/unreleased/Refactor-gl_dropdown-js.yml
+5
-0
No files found.
app/assets/javascripts/gl_dropdown.js
View file @
79f0044d
/* eslint-disable
func-names, no-underscore-dangle, one-var, no-cond-assign, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, no-param-reassign, no-loop-func
*/
/* eslint-disable
one-var, consistent-return
*/
import
$
from
'
jquery
'
;
import
_
from
'
underscore
'
;
...
...
@@ -32,121 +32,124 @@ const FILTER_INPUT = '.dropdown-input .dropdown-input-field:not(.dropdown-no-fil
const
NO_FILTER_INPUT
=
'
.dropdown-input .dropdown-input-field.dropdown-no-filter
'
;
function
GitLabDropdownInput
(
input
,
options
)
{
const
_this
=
this
;
this
.
input
=
input
;
this
.
options
=
options
;
this
.
fieldName
=
this
.
options
.
fieldName
||
'
field-name
'
;
const
$inputContainer
=
this
.
input
.
parent
();
const
$clearButton
=
$inputContainer
.
find
(
'
.js-dropdown-input-clear
'
);
$clearButton
.
on
(
'
click
'
,
e
=>
{
// Clear click
e
.
preventDefault
();
e
.
stopPropagation
();
return
this
.
input
.
val
(
''
)
.
trigger
(
'
input
'
)
.
focus
();
});
this
.
input
.
on
(
'
keydown
'
,
e
=>
{
const
keyCode
=
e
.
which
;
if
(
keyCode
===
13
&&
!
options
.
elIsInput
)
{
e
.
preventDefault
();
}
})
.
on
(
'
input
'
,
e
=>
{
let
val
=
e
.
currentTarget
.
value
||
_this
.
options
.
inputFieldName
;
val
=
val
.
split
(
'
'
)
.
join
(
'
-
'
)
// replaces space with dash
.
replace
(
/
[^
a-zA-Z0-9 -
]
/g
,
''
)
.
toLowerCase
()
// replace non alphanumeric
.
replace
(
/
(
-
)\1
+/g
,
'
-
'
);
// replace repeated dashes
_this
.
cb
(
_this
.
options
.
fieldName
,
val
,
{},
true
);
_this
.
input
.
closest
(
'
.dropdown
'
)
.
find
(
'
.dropdown-toggle-text
'
)
.
text
(
val
);
class
GitLabDropdownInput
{
constructor
(
input
,
options
)
{
this
.
input
=
input
;
this
.
options
=
options
;
this
.
fieldName
=
this
.
options
.
fieldName
||
'
field-name
'
;
const
$inputContainer
=
this
.
input
.
parent
();
const
$clearButton
=
$inputContainer
.
find
(
'
.js-dropdown-input-clear
'
);
$clearButton
.
on
(
'
click
'
,
e
=>
{
// Clear click
e
.
preventDefault
();
e
.
stopPropagation
();
return
this
.
input
.
val
(
''
)
.
trigger
(
'
input
'
)
.
focus
();
});
}
GitLabDropdownInput
.
prototype
.
onInput
=
function
(
cb
)
{
this
.
cb
=
cb
;
};
this
.
input
.
on
(
'
keydown
'
,
e
=>
{
const
keyCode
=
e
.
which
;
if
(
keyCode
===
13
&&
!
options
.
elIsInput
)
{
e
.
preventDefault
();
}
})
.
on
(
'
input
'
,
e
=>
{
let
val
=
e
.
currentTarget
.
value
||
this
.
options
.
inputFieldName
;
val
=
val
.
split
(
'
'
)
.
join
(
'
-
'
)
// replaces space with dash
.
replace
(
/
[^
a-zA-Z0-9 -
]
/g
,
''
)
.
toLowerCase
()
// replace non alphanumeric
.
replace
(
/
(
-
)\1
+/g
,
'
-
'
);
// replace repeated dashes
this
.
cb
(
this
.
options
.
fieldName
,
val
,
{},
true
);
this
.
input
.
closest
(
'
.dropdown
'
)
.
find
(
'
.dropdown-toggle-text
'
)
.
text
(
val
);
});
}
function
GitLabDropdownFilter
(
input
,
options
)
{
let
ref
,
timeout
;
this
.
input
=
input
;
this
.
options
=
options
;
this
.
filterInputBlur
=
(
ref
=
this
.
options
.
filterInputBlur
)
!=
null
?
ref
:
true
;
const
$inputContainer
=
this
.
input
.
parent
();
const
$clearButton
=
$inputContainer
.
find
(
'
.js-dropdown-input-clear
'
);
$clearButton
.
on
(
'
click
'
,
e
=>
{
// Clear click
e
.
preventDefault
();
e
.
stopPropagation
();
return
this
.
input
.
val
(
''
)
.
trigger
(
'
input
'
)
.
focus
();
});
// Key events
timeout
=
''
;
this
.
input
.
on
(
'
keydown
'
,
e
=>
{
const
keyCode
=
e
.
which
;
if
(
keyCode
===
13
&&
!
options
.
elIsInput
)
{
e
.
preventDefault
();
}
})
.
on
(
'
input
'
,
()
=>
{
if
(
this
.
input
.
val
()
!==
''
&&
!
$inputContainer
.
hasClass
(
HAS_VALUE_CLASS
))
{
$inputContainer
.
addClass
(
HAS_VALUE_CLASS
);
}
else
if
(
this
.
input
.
val
()
===
''
&&
$inputContainer
.
hasClass
(
HAS_VALUE_CLASS
))
{
$inputContainer
.
removeClass
(
HAS_VALUE_CLASS
);
}
// Only filter asynchronously only if option remote is set
if
(
this
.
options
.
remote
)
{
clearTimeout
(
timeout
);
return
(
timeout
=
setTimeout
(()
=>
{
$inputContainer
.
parent
().
addClass
(
'
is-loading
'
);
return
this
.
options
.
query
(
this
.
input
.
val
(),
data
=>
{
$inputContainer
.
parent
().
removeClass
(
'
is-loading
'
);
return
this
.
options
.
callback
(
data
);
});
},
250
));
}
else
{
return
this
.
filter
(
this
.
input
.
val
());
}
});
onInput
(
cb
)
{
this
.
cb
=
cb
;
}
}
GitLabDropdownFilter
.
prototype
.
shouldBlur
=
function
(
keyCode
)
{
return
BLUR_KEYCODES
.
indexOf
(
keyCode
)
!==
-
1
;
};
class
GitLabDropdownFilter
{
constructor
(
input
,
options
)
{
let
ref
,
timeout
;
this
.
input
=
input
;
this
.
options
=
options
;
// eslint-disable-next-line no-cond-assign
this
.
filterInputBlur
=
(
ref
=
this
.
options
.
filterInputBlur
)
!=
null
?
ref
:
true
;
const
$inputContainer
=
this
.
input
.
parent
();
const
$clearButton
=
$inputContainer
.
find
(
'
.js-dropdown-input-clear
'
);
$clearButton
.
on
(
'
click
'
,
e
=>
{
// Clear click
e
.
preventDefault
();
e
.
stopPropagation
();
return
this
.
input
.
val
(
''
)
.
trigger
(
'
input
'
)
.
focus
();
});
// Key events
timeout
=
''
;
this
.
input
.
on
(
'
keydown
'
,
e
=>
{
const
keyCode
=
e
.
which
;
if
(
keyCode
===
13
&&
!
options
.
elIsInput
)
{
e
.
preventDefault
();
}
})
.
on
(
'
input
'
,
()
=>
{
if
(
this
.
input
.
val
()
!==
''
&&
!
$inputContainer
.
hasClass
(
HAS_VALUE_CLASS
))
{
$inputContainer
.
addClass
(
HAS_VALUE_CLASS
);
}
else
if
(
this
.
input
.
val
()
===
''
&&
$inputContainer
.
hasClass
(
HAS_VALUE_CLASS
))
{
$inputContainer
.
removeClass
(
HAS_VALUE_CLASS
);
}
// Only filter asynchronously only if option remote is set
if
(
this
.
options
.
remote
)
{
clearTimeout
(
timeout
);
// eslint-disable-next-line no-return-assign
return
(
timeout
=
setTimeout
(()
=>
{
$inputContainer
.
parent
().
addClass
(
'
is-loading
'
);
return
this
.
options
.
query
(
this
.
input
.
val
(),
data
=>
{
$inputContainer
.
parent
().
removeClass
(
'
is-loading
'
);
return
this
.
options
.
callback
(
data
);
});
},
250
));
}
return
this
.
filter
(
this
.
input
.
val
());
});
}
GitLabDropdownFilter
.
prototype
.
filter
=
function
(
search_text
)
{
let
elements
,
group
,
key
,
results
,
tmp
;
if
(
this
.
options
.
onFilter
)
{
this
.
options
.
onFilter
(
search_text
);
}
const
data
=
this
.
options
.
data
();
if
(
data
!=
null
&&
!
this
.
options
.
filterByText
)
{
results
=
data
;
if
(
search_text
!==
''
)
{
// When data is an array of objects therefore [object Array] e.g.
// [
// { prop: 'foo' },
// { prop: 'baz' }
// ]
if
(
_
.
isArray
(
data
))
{
results
=
fuzzaldrinPlus
.
filter
(
data
,
search_text
,
{
key
:
this
.
options
.
keys
,
});
}
else
{
static
shouldBlur
(
keyCode
)
{
return
BLUR_KEYCODES
.
indexOf
(
keyCode
)
!==
-
1
;
}
filter
(
searchText
)
{
let
group
,
results
,
tmp
;
if
(
this
.
options
.
onFilter
)
{
this
.
options
.
onFilter
(
searchText
);
}
const
data
=
this
.
options
.
data
();
if
(
data
!=
null
&&
!
this
.
options
.
filterByText
)
{
results
=
data
;
if
(
searchText
!==
''
)
{
// When data is an array of objects therefore [object Array] e.g.
// [
// { prop: 'foo' },
// { prop: 'baz' }
// ]
if
(
_
.
isArray
(
data
))
{
results
=
fuzzaldrinPlus
.
filter
(
data
,
searchText
,
{
key
:
this
.
options
.
keys
,
});
}
// If data is grouped therefore an [object Object]. e.g.
// {
// groupName1: [
...
...
@@ -158,33 +161,32 @@ GitLabDropdownFilter.prototype.filter = function(search_text) {
// { prop: 'def' }
// ]
// }
if
(
isObject
(
data
))
{
else
if
(
isObject
(
data
))
{
results
=
{};
for
(
key
in
data
)
{
Object
.
keys
(
data
).
forEach
(
key
=>
{
group
=
data
[
key
];
tmp
=
fuzzaldrinPlus
.
filter
(
group
,
search
_t
ext
,
{
tmp
=
fuzzaldrinPlus
.
filter
(
group
,
search
T
ext
,
{
key
:
this
.
options
.
keys
,
});
if
(
tmp
.
length
)
{
results
[
key
]
=
tmp
.
map
(
item
=>
item
);
}
}
}
);
}
}
return
this
.
options
.
callback
(
results
);
}
return
this
.
options
.
callback
(
results
);
}
else
{
elements
=
this
.
options
.
elements
();
if
(
search_text
)
{
const
elements
=
this
.
options
.
elements
();
if
(
searchText
)
{
// eslint-disable-next-line func-names
elements
.
each
(
function
()
{
const
$el
=
$
(
this
);
const
matches
=
fuzzaldrinPlus
.
match
(
$el
.
text
().
trim
(),
search
_t
ext
);
const
matches
=
fuzzaldrinPlus
.
match
(
$el
.
text
().
trim
(),
search
T
ext
);
if
(
!
$el
.
is
(
'
.dropdown-header
'
))
{
if
(
matches
.
length
)
{
return
$el
.
show
().
removeClass
(
'
option-hidden
'
);
}
else
{
return
$el
.
hide
().
addClass
(
'
option-hidden
'
);
}
return
$el
.
hide
().
addClass
(
'
option-hidden
'
);
}
});
}
else
{
...
...
@@ -196,235 +198,240 @@ GitLabDropdownFilter.prototype.filter = function(search_text) {
.
find
(
'
.dropdown-menu-empty-item
'
)
.
toggleClass
(
'
hidden
'
,
elements
.
is
(
'
:visible
'
));
}
};
function
GitLabDropdownRemote
(
dataEndpoint
,
options
)
{
this
.
dataEndpoint
=
dataEndpoint
;
this
.
options
=
options
;
}
GitLabDropdownRemote
.
prototype
.
execute
=
function
()
{
if
(
typeof
this
.
dataEndpoint
===
'
string
'
)
{
return
this
.
fetchData
();
}
else
if
(
typeof
this
.
dataEndpoint
===
'
function
'
)
{
class
GitLabDropdownRemote
{
constructor
(
dataEndpoint
,
options
)
{
this
.
dataEndpoint
=
dataEndpoint
;
this
.
options
=
options
;
}
execute
()
{
if
(
typeof
this
.
dataEndpoint
===
'
string
'
)
{
return
this
.
fetchData
();
}
else
if
(
typeof
this
.
dataEndpoint
===
'
function
'
)
{
if
(
this
.
options
.
beforeSend
)
{
this
.
options
.
beforeSend
();
}
return
this
.
dataEndpoint
(
''
,
data
=>
{
// Fetch the data by calling the data function
if
(
this
.
options
.
success
)
{
this
.
options
.
success
(
data
);
}
if
(
this
.
options
.
beforeSend
)
{
return
this
.
options
.
beforeSend
();
}
});
}
}
fetchData
()
{
if
(
this
.
options
.
beforeSend
)
{
this
.
options
.
beforeSend
();
}
return
this
.
dataEndpoint
(
''
,
data
=>
{
// Fetch the data by calling the data function
// Fetch the data through ajax if the data is a string
return
axios
.
get
(
this
.
dataEndpoint
).
then
(({
data
})
=>
{
if
(
this
.
options
.
success
)
{
this
.
options
.
success
(
data
);
}
if
(
this
.
options
.
beforeSend
)
{
return
this
.
options
.
beforeSend
();
return
this
.
options
.
success
(
data
);
}
});
}
};
GitLabDropdownRemote
.
prototype
.
fetchData
=
function
()
{
if
(
this
.
options
.
beforeSend
)
{
this
.
options
.
beforeSend
();
}
}
// Fetch the data through ajax if the data is a string
return
axios
.
get
(
this
.
dataEndpoint
).
then
(({
data
})
=>
{
if
(
this
.
options
.
success
)
{
return
this
.
options
.
success
(
data
);
class
GitLabDropdown
{
constructor
(
el1
,
options
)
{
let
selector
,
self
;
this
.
el
=
el1
;
this
.
options
=
options
;
this
.
updateLabel
=
this
.
updateLabel
.
bind
(
this
);
this
.
hidden
=
this
.
hidden
.
bind
(
this
);
this
.
opened
=
this
.
opened
.
bind
(
this
);
this
.
shouldPropagate
=
this
.
shouldPropagate
.
bind
(
this
);
self
=
this
;
selector
=
$
(
this
.
el
).
data
(
'
target
'
);
this
.
dropdown
=
selector
!=
null
?
$
(
selector
)
:
$
(
this
.
el
).
parent
();
// Set Defaults
this
.
filterInput
=
this
.
options
.
filterInput
||
this
.
getElement
(
FILTER_INPUT
);
this
.
noFilterInput
=
this
.
options
.
noFilterInput
||
this
.
getElement
(
NO_FILTER_INPUT
);
this
.
highlight
=
Boolean
(
this
.
options
.
highlight
);
this
.
icon
=
Boolean
(
this
.
options
.
icon
);
this
.
filterInputBlur
=
this
.
options
.
filterInputBlur
!=
null
?
this
.
options
.
filterInputBlur
:
true
;
// If no input is passed create a default one
self
=
this
;
// If selector was passed
if
(
_
.
isString
(
this
.
filterInput
))
{
this
.
filterInput
=
this
.
getElement
(
this
.
filterInput
);
}
});
};
function
GitLabDropdown
(
el1
,
options
)
{
let
selector
,
self
;
this
.
el
=
el1
;
this
.
options
=
options
;
this
.
updateLabel
=
this
.
updateLabel
.
bind
(
this
);
this
.
hidden
=
this
.
hidden
.
bind
(
this
);
this
.
opened
=
this
.
opened
.
bind
(
this
);
this
.
shouldPropagate
=
this
.
shouldPropagate
.
bind
(
this
);
self
=
this
;
selector
=
$
(
this
.
el
).
data
(
'
target
'
);
this
.
dropdown
=
selector
!=
null
?
$
(
selector
)
:
$
(
this
.
el
).
parent
();
// Set Defaults
this
.
filterInput
=
this
.
options
.
filterInput
||
this
.
getElement
(
FILTER_INPUT
);
this
.
noFilterInput
=
this
.
options
.
noFilterInput
||
this
.
getElement
(
NO_FILTER_INPUT
);
this
.
highlight
=
Boolean
(
this
.
options
.
highlight
);
this
.
icon
=
Boolean
(
this
.
options
.
icon
);
this
.
filterInputBlur
=
this
.
options
.
filterInputBlur
!=
null
?
this
.
options
.
filterInputBlur
:
true
;
// If no input is passed create a default one
self
=
this
;
// If selector was passed
if
(
_
.
isString
(
this
.
filterInput
))
{
this
.
filterInput
=
this
.
getElement
(
this
.
filterInput
);
}
const
searchFields
=
this
.
options
.
search
?
this
.
options
.
search
.
fields
:
[];
if
(
this
.
options
.
data
)
{
// If we provided data
// data could be an array of objects or a group of arrays
if
(
_
.
isObject
(
this
.
options
.
data
)
&&
!
_
.
isFunction
(
this
.
options
.
data
))
{
this
.
fullData
=
this
.
options
.
data
;
currentIndex
=
-
1
;
this
.
parseData
(
this
.
options
.
data
);
this
.
focusTextInput
();
}
else
{
this
.
remote
=
new
GitLabDropdownRemote
(
this
.
options
.
data
,
{
dataType
:
this
.
options
.
dataType
,
beforeSend
:
this
.
toggleLoading
.
bind
(
this
),
success
:
data
=>
{
this
.
fullData
=
data
;
this
.
parseData
(
this
.
fullData
);
this
.
focusTextInput
();
// Update dropdown position since remote data may have changed dropdown size
this
.
dropdown
.
find
(
'
.dropdown-menu-toggle
'
).
dropdown
(
'
update
'
);
if
(
this
.
options
.
filterable
&&
this
.
filter
&&
this
.
filter
.
input
&&
this
.
filter
.
input
.
val
()
&&
this
.
filter
.
input
.
val
().
trim
()
!==
''
)
{
return
this
.
filter
.
input
.
trigger
(
'
input
'
);
}
},
instance
:
this
,
});
const
searchFields
=
this
.
options
.
search
?
this
.
options
.
search
.
fields
:
[];
if
(
this
.
options
.
data
)
{
// If we provided data
// data could be an array of objects or a group of arrays
if
(
_
.
isObject
(
this
.
options
.
data
)
&&
!
_
.
isFunction
(
this
.
options
.
data
))
{
this
.
fullData
=
this
.
options
.
data
;
currentIndex
=
-
1
;
this
.
parseData
(
this
.
options
.
data
);
this
.
focusTextInput
();
}
else
{
this
.
remote
=
new
GitLabDropdownRemote
(
this
.
options
.
data
,
{
dataType
:
this
.
options
.
dataType
,
beforeSend
:
this
.
toggleLoading
.
bind
(
this
),
success
:
data
=>
{
this
.
fullData
=
data
;
this
.
parseData
(
this
.
fullData
);
this
.
focusTextInput
();
// Update dropdown position since remote data may have changed dropdown size
this
.
dropdown
.
find
(
'
.dropdown-menu-toggle
'
).
dropdown
(
'
update
'
);
if
(
this
.
options
.
filterable
&&
this
.
filter
&&
this
.
filter
.
input
&&
this
.
filter
.
input
.
val
()
&&
this
.
filter
.
input
.
val
().
trim
()
!==
''
)
{
return
this
.
filter
.
input
.
trigger
(
'
input
'
);
}
},
instance
:
this
,
});
}
}
}
if
(
this
.
noFilterInput
.
length
)
{
this
.
plainInput
=
new
GitLabDropdownInput
(
this
.
noFilterInput
,
this
.
options
);
this
.
plainInput
.
onInput
(
this
.
addInput
.
bind
(
this
));
}
// Init filterable
if
(
this
.
options
.
filterable
)
{
this
.
filter
=
new
GitLabDropdownFilter
(
this
.
filterInput
,
{
elIsInput
:
$
(
this
.
el
).
is
(
'
input
'
),
filterInputBlur
:
this
.
filterInputBlur
,
filterByText
:
this
.
options
.
filterByText
,
onFilter
:
this
.
options
.
onFilter
,
remote
:
this
.
options
.
filterRemote
,
query
:
this
.
options
.
data
,
keys
:
searchFields
,
instance
:
this
,
elements
:
()
=>
{
selector
=
`.dropdown-content li:not(
${
NON_SELECTABLE_CLASSES
}
)`
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
return
$
(
selector
,
this
.
dropdown
);
},
data
:
()
=>
this
.
fullData
,
callback
:
data
=>
{
this
.
parseData
(
data
);
if
(
this
.
filterInput
.
val
()
!==
''
)
{
selector
=
SELECTABLE_CLASSES
;
if
(
this
.
noFilterInput
.
length
)
{
this
.
plainInput
=
new
GitLabDropdownInput
(
this
.
noFilterInput
,
this
.
options
);
this
.
plainInput
.
onInput
(
this
.
addInput
.
bind
(
this
));
}
// Init filterable
if
(
this
.
options
.
filterable
)
{
this
.
filter
=
new
GitLabDropdownFilter
(
this
.
filterInput
,
{
elIsInput
:
$
(
this
.
el
).
is
(
'
input
'
),
filterInputBlur
:
this
.
filterInputBlur
,
filterByText
:
this
.
options
.
filterByText
,
onFilter
:
this
.
options
.
onFilter
,
remote
:
this
.
options
.
filterRemote
,
query
:
this
.
options
.
data
,
keys
:
searchFields
,
instance
:
this
,
elements
:
()
=>
{
selector
=
`.dropdown-content li:not(
${
NON_SELECTABLE_CLASSES
}
)`
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
if
(
$
(
this
.
el
).
is
(
'
input
'
))
{
currentIndex
=
-
1
;
}
else
{
$
(
selector
,
this
.
dropdown
)
.
first
()
.
find
(
'
a
'
)
.
addClass
(
'
is-focused
'
);
currentIndex
=
0
;
return
$
(
selector
,
this
.
dropdown
);
},
data
:
()
=>
this
.
fullData
,
callback
:
data
=>
{
this
.
parseData
(
data
);
if
(
this
.
filterInput
.
val
()
!==
''
)
{
selector
=
SELECTABLE_CLASSES
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
if
(
$
(
this
.
el
).
is
(
'
input
'
))
{
currentIndex
=
-
1
;
}
else
{
$
(
selector
,
this
.
dropdown
)
.
first
()
.
find
(
'
a
'
)
.
addClass
(
'
is-focused
'
);
currentIndex
=
0
;
}
}
}
},
});
}
// Event listeners
this
.
dropdown
.
on
(
'
shown.bs.dropdown
'
,
this
.
opened
);
this
.
dropdown
.
on
(
'
hidden.bs.dropdown
'
,
this
.
hidden
);
$
(
this
.
el
).
on
(
'
update.label
'
,
this
.
updateLabel
);
this
.
dropdown
.
on
(
'
click
'
,
'
.dropdown-menu, .dropdown-menu-close
'
,
this
.
shouldPropagate
);
this
.
dropdown
.
on
(
'
keyup
'
,
e
=>
{
// Escape key
if
(
e
.
which
===
27
)
{
return
$
(
'
.dropdown-menu-close
'
,
this
.
dropdown
).
trigger
(
'
click
'
);
},
});
}
});
this
.
dropdown
.
on
(
'
blur
'
,
'
a
'
,
e
=>
{
let
$dropdownMenu
,
$relatedTarget
;
if
(
e
.
relatedTarget
!=
null
)
{
$relatedTarget
=
$
(
e
.
relatedTarget
);
$dropdownMenu
=
$relatedTarget
.
closest
(
'
.dropdown-menu
'
);
if
(
$dropdownMenu
.
length
===
0
)
{
return
this
.
dropdown
.
removeClass
(
'
show
'
);
// Event listeners
this
.
dropdown
.
on
(
'
shown.bs.dropdown
'
,
this
.
opened
);
this
.
dropdown
.
on
(
'
hidden.bs.dropdown
'
,
this
.
hidden
);
$
(
this
.
el
).
on
(
'
update.label
'
,
this
.
updateLabel
);
this
.
dropdown
.
on
(
'
click
'
,
'
.dropdown-menu, .dropdown-menu-close
'
,
this
.
shouldPropagate
);
this
.
dropdown
.
on
(
'
keyup
'
,
e
=>
{
// Escape key
if
(
e
.
which
===
27
)
{
return
$
(
'
.dropdown-menu-close
'
,
this
.
dropdown
).
trigger
(
'
click
'
);
}
});
this
.
dropdown
.
on
(
'
blur
'
,
'
a
'
,
e
=>
{
let
$dropdownMenu
,
$relatedTarget
;
if
(
e
.
relatedTarget
!=
null
)
{
$relatedTarget
=
$
(
e
.
relatedTarget
);
$dropdownMenu
=
$relatedTarget
.
closest
(
'
.dropdown-menu
'
);
if
(
$dropdownMenu
.
length
===
0
)
{
return
this
.
dropdown
.
removeClass
(
'
show
'
);
}
}
}
});
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
this
.
dropdown
.
find
(
'
.dropdown-toggle-page, .dropdown-menu-back
'
).
on
(
'
click
'
,
e
=>
{
e
.
preventDefault
();
e
.
stopPropagation
();
return
this
.
togglePage
();
});
}
if
(
this
.
options
.
selectable
)
{
selector
=
'
.dropdown-content a
'
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
'
.dropdown-page-one .dropdown-content a
'
;
}
this
.
dropdown
.
on
(
'
click
'
,
selector
,
e
=>
{
const
$el
=
$
(
e
.
currentTarget
);
const
selected
=
self
.
rowClicked
(
$el
);
const
selectedObj
=
selected
?
selected
[
0
]
:
null
;
const
isMarking
=
selected
?
selected
[
1
]
:
null
;
if
(
this
.
options
.
clicked
)
{
this
.
options
.
clicked
.
call
(
this
,
{
selectedObj
,
$el
,
e
,
isMarking
,
});
this
.
dropdown
.
find
(
'
.dropdown-toggle-page, .dropdown-menu-back
'
).
on
(
'
click
'
,
e
=>
{
e
.
preventDefault
();
e
.
stopPropagation
();
return
this
.
togglePage
();
});
}
if
(
this
.
options
.
selectable
)
{
selector
=
'
.dropdown-content a
'
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
'
.dropdown-page-one .dropdown-content a
'
;
}
this
.
dropdown
.
on
(
'
click
'
,
selector
,
e
=>
{
const
$el
=
$
(
e
.
currentTarget
);
const
selected
=
self
.
rowClicked
(
$el
);
const
selectedObj
=
selected
?
selected
[
0
]
:
null
;
const
isMarking
=
selected
?
selected
[
1
]
:
null
;
if
(
this
.
options
.
clicked
)
{
this
.
options
.
clicked
.
call
(
this
,
{
selectedObj
,
$el
,
e
,
isMarking
,
});
}
// Update label right after all modifications in dropdown has been done
if
(
this
.
options
.
toggleLabel
)
{
this
.
updateLabel
(
selectedObj
,
$el
,
this
);
}
// Update label right after all modifications in dropdown has been done
if
(
this
.
options
.
toggleLabel
)
{
this
.
updateLabel
(
selectedObj
,
$el
,
this
);
}
$el
.
trigger
(
'
blur
'
);
});
$el
.
trigger
(
'
blur
'
);
});
}
}
}
// Finds an element inside wrapper element
GitLabDropdown
.
prototype
.
getElement
=
function
(
selector
)
{
return
this
.
dropdown
.
find
(
selector
);
};
// Finds an element inside wrapper element
getElement
(
selector
)
{
return
this
.
dropdown
.
find
(
selector
);
}
GitLabDropdown
.
prototype
.
toggleLoading
=
function
()
{
return
$
(
'
.dropdown-menu
'
,
this
.
dropdown
).
toggleClass
(
LOADING_CLASS
);
};
toggleLoading
()
{
return
$
(
'
.dropdown-menu
'
,
this
.
dropdown
).
toggleClass
(
LOADING_CLASS
);
}
GitLabDropdown
.
prototype
.
togglePage
=
function
()
{
const
menu
=
$
(
'
.dropdown-menu
'
,
this
.
dropdown
);
if
(
menu
.
hasClass
(
PAGE_TWO_CLASS
))
{
if
(
this
.
remote
)
{
this
.
remote
.
execute
();
togglePage
()
{
const
menu
=
$
(
'
.dropdown-menu
'
,
this
.
dropdown
);
if
(
menu
.
hasClass
(
PAGE_TWO_CLASS
))
{
if
(
this
.
remote
)
{
this
.
remote
.
execute
();
}
}
menu
.
toggleClass
(
PAGE_TWO_CLASS
);
// Focus first visible input on active page
return
this
.
dropdown
.
find
(
'
[class^="dropdown-page-"]:visible :text:visible:first
'
).
focus
();
}
menu
.
toggleClass
(
PAGE_TWO_CLASS
);
// Focus first visible input on active page
return
this
.
dropdown
.
find
(
'
[class^="dropdown-page-"]:visible :text:visible:first
'
).
focus
();
};
GitLabDropdown
.
prototype
.
parseData
=
function
(
data
)
{
let
groupData
,
html
,
name
;
this
.
renderedData
=
data
;
if
(
this
.
options
.
filterable
&&
data
.
length
===
0
)
{
// render no matching results
html
=
[
this
.
noResults
()];
}
else
{
parseData
(
data
)
{
let
groupData
,
html
;
this
.
renderedData
=
data
;
if
(
this
.
options
.
filterable
&&
data
.
length
===
0
)
{
// render no matching results
html
=
[
this
.
noResults
()];
}
// Handle array groups
if
(
isObject
(
data
))
{
else
if
(
isObject
(
data
))
{
html
=
[];
for
(
name
in
data
)
{
Object
.
keys
(
data
).
forEach
(
name
=>
{
groupData
=
data
[
name
];
html
.
push
(
this
.
renderItem
(
...
...
@@ -436,461 +443,455 @@ GitLabDropdown.prototype.parseData = function(data) {
),
);
this
.
renderData
(
groupData
,
name
).
map
(
item
=>
html
.
push
(
item
));
}
}
);
}
else
{
// Render each row
html
=
this
.
renderData
(
data
);
}
}
// Render the full menu
const
full_html
=
this
.
renderMenu
(
h
tml
);
return
this
.
appendMenu
(
full_html
);
};
GitLabDropdown
.
prototype
.
renderData
=
function
(
data
,
group
)
{
return
data
.
map
((
obj
,
index
)
=>
this
.
renderItem
(
obj
,
group
||
false
,
index
));
};
GitLabDropdown
.
prototype
.
shouldPropagate
=
function
(
e
)
{
let
$target
;
if
(
this
.
options
.
multiSelect
||
this
.
options
.
shouldPropagate
===
false
)
{
$target
=
$
(
e
.
target
);
if
(
$target
&&
!
$target
.
hasClass
(
'
dropdown-menu-close
'
)
&&
!
$target
.
hasClass
(
'
dropdown-menu-close-icon
'
)
&&
!
$target
.
data
(
'
isLink
'
)
)
{
e
.
stopPropagation
();
// This prevents automatic scrolling to the top
if
(
$target
.
closest
(
'
a
'
).
length
)
{
return
false
;
// Render the full menu
const
fullHtml
=
this
.
renderMenu
(
html
);
return
this
.
appendMenu
(
fullH
tml
);
}
renderData
(
data
,
group
)
{
return
data
.
map
((
obj
,
index
)
=>
this
.
renderItem
(
obj
,
group
||
false
,
index
));
}
shouldPropagate
(
e
)
{
let
$target
;
if
(
this
.
options
.
multiSelect
||
this
.
options
.
shouldPropagate
===
false
)
{
$target
=
$
(
e
.
target
);
if
(
$target
&&
!
$target
.
hasClass
(
'
dropdown-menu-close
'
)
&&
!
$target
.
hasClass
(
'
dropdown-menu-close-icon
'
)
&&
!
$target
.
data
(
'
isLink
'
)
)
{
e
.
stopPropagation
();
// This prevents automatic scrolling to the top
if
(
$target
.
closest
(
'
a
'
).
length
)
{
return
false
;
}
}
}
return
true
;
return
true
;
}
}
};
GitLabDropdown
.
prototype
.
filteredFullData
=
function
()
{
return
this
.
fullData
.
filter
(
r
=>
typeof
r
===
'
object
'
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
beforeDivider
'
)
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
header
'
),
);
};
GitLabDropdown
.
prototype
.
opened
=
function
(
e
)
{
this
.
resetRows
();
this
.
addArrowKeyEvent
();
const
dropdownToggle
=
this
.
dropdown
.
find
(
'
.dropdown-menu-toggle
'
);
const
hasFilterBulkUpdate
=
dropdownToggle
.
hasClass
(
'
js-filter-bulk-update
'
);
const
shouldRefreshOnOpen
=
dropdownToggle
.
hasClass
(
'
js-gl-dropdown-refresh-on-open
'
);
const
hasMultiSelect
=
dropdownToggle
.
hasClass
(
'
js-multiselect
'
);
// Makes indeterminate items effective
if
(
this
.
fullData
&&
(
shouldRefreshOnOpen
||
hasFilterBulkUpdate
))
{
this
.
parseData
(
this
.
fullData
);
}
// Process the data to make sure rendered data
// matches the correct layout
const
inputValue
=
this
.
filterInput
.
val
();
if
(
this
.
fullData
&&
hasMultiSelect
&&
this
.
options
.
processData
&&
inputValue
.
length
===
0
)
{
this
.
options
.
processData
.
call
(
this
.
options
,
inputValue
,
this
.
filteredFullData
(),
this
.
parseData
.
bind
(
this
),
filteredFullData
()
{
return
this
.
fullData
.
filter
(
r
=>
typeof
r
===
'
object
'
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
beforeDivider
'
)
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
header
'
),
);
}
const
contentHtml
=
$
(
'
.dropdown-content
'
,
this
.
dropdown
).
html
();
if
(
this
.
remote
&&
contentHtml
===
''
)
{
this
.
remote
.
execute
();
}
else
{
this
.
focusTextInput
();
}
opened
(
e
)
{
this
.
resetRows
();
this
.
addArrowKeyEvent
();
if
(
this
.
options
.
showMenuAbove
)
{
this
.
positionMenuAbove
();
}
const
dropdownToggle
=
this
.
dropdown
.
find
(
'
.dropdown-menu-toggle
'
);
const
hasFilterBulkUpdate
=
dropdownToggle
.
hasClass
(
'
js-filter-bulk-update
'
);
const
shouldRefreshOnOpen
=
dropdownToggle
.
hasClass
(
'
js-gl-dropdown-refresh-on-open
'
);
const
hasMultiSelect
=
dropdownToggle
.
hasClass
(
'
js-multiselect
'
);
if
(
this
.
options
.
opened
)
{
if
(
this
.
options
.
preserveContext
)
{
this
.
options
.
opened
(
e
);
}
else
{
this
.
options
.
opened
.
call
(
this
,
e
);
// Makes indeterminate items effective
if
(
this
.
fullData
&&
(
shouldRefreshOnOpen
||
hasFilterBulkUpdate
))
{
this
.
parseData
(
this
.
fullData
);
}
}
return
this
.
dropdown
.
trigger
(
'
shown.gl.dropdown
'
);
};
// Process the data to make sure rendered data
// matches the correct layout
const
inputValue
=
this
.
filterInput
.
val
();
if
(
this
.
fullData
&&
hasMultiSelect
&&
this
.
options
.
processData
&&
inputValue
.
length
===
0
)
{
this
.
options
.
processData
.
call
(
this
.
options
,
inputValue
,
this
.
filteredFullData
(),
this
.
parseData
.
bind
(
this
),
);
}
GitLabDropdown
.
prototype
.
positionMenuAbove
=
function
()
{
const
$menu
=
this
.
dropdown
.
find
(
'
.dropdown-menu
'
);
const
contentHtml
=
$
(
'
.dropdown-content
'
,
this
.
dropdown
).
html
();
if
(
this
.
remote
&&
contentHtml
===
''
)
{
this
.
remote
.
execute
();
}
else
{
this
.
focusTextInput
();
}
$menu
.
addClass
(
'
dropdown-open-top
'
);
$menu
.
css
(
'
top
'
,
'
initial
'
);
$menu
.
css
(
'
bottom
'
,
'
100%
'
);
};
if
(
this
.
options
.
showMenuAbove
)
{
this
.
positionMenuAbove
();
}
if
(
this
.
options
.
opened
)
{
if
(
this
.
options
.
preserveContext
)
{
this
.
options
.
opened
(
e
);
}
else
{
this
.
options
.
opened
.
call
(
this
,
e
);
}
}
GitLabDropdown
.
prototype
.
hidden
=
function
(
e
)
{
this
.
resetRows
();
this
.
removeArrowKeyEvent
();
const
$input
=
this
.
dropdown
.
find
(
'
.dropdown-input-field
'
);
if
(
this
.
options
.
filterable
)
{
$input
.
blur
();
return
this
.
dropdown
.
trigger
(
'
shown.gl.dropdown
'
);
}
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
$
(
'
.dropdown-menu
'
,
this
.
dropdown
).
removeClass
(
PAGE_TWO_CLASS
);
positionMenuAbove
()
{
const
$menu
=
this
.
dropdown
.
find
(
'
.dropdown-menu
'
);
$menu
.
addClass
(
'
dropdown-open-top
'
);
$menu
.
css
(
'
top
'
,
'
initial
'
);
$menu
.
css
(
'
bottom
'
,
'
100%
'
);
}
if
(
this
.
options
.
hidden
)
{
this
.
options
.
hidden
.
call
(
this
,
e
);
hidden
(
e
)
{
this
.
resetRows
();
this
.
removeArrowKeyEvent
();
const
$input
=
this
.
dropdown
.
find
(
'
.dropdown-input-field
'
);
if
(
this
.
options
.
filterable
)
{
$input
.
blur
();
}
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
$
(
'
.dropdown-menu
'
,
this
.
dropdown
).
removeClass
(
PAGE_TWO_CLASS
);
}
if
(
this
.
options
.
hidden
)
{
this
.
options
.
hidden
.
call
(
this
,
e
);
}
return
this
.
dropdown
.
trigger
(
'
hidden.gl.dropdown
'
);
}
return
this
.
dropdown
.
trigger
(
'
hidden.gl.dropdown
'
);
};
// Render the full menu
GitLabDropdown
.
prototype
.
renderMenu
=
function
(
html
)
{
if
(
this
.
options
.
renderMenu
)
{
return
this
.
options
.
renderMenu
(
html
);
}
else
{
// Render the full menu
renderMenu
(
html
)
{
if
(
this
.
options
.
renderMenu
)
{
return
this
.
options
.
renderMenu
(
html
);
}
return
$
(
'
<ul>
'
).
append
(
html
);
}
};
// Append the menu into the dropdown
GitLabDropdown
.
prototype
.
appendMenu
=
function
(
html
)
{
return
this
.
clearMenu
().
append
(
html
);
};
// Append the menu into the dropdown
appendMenu
(
html
)
{
return
this
.
clearMenu
().
append
(
html
);
}
GitLabDropdown
.
prototype
.
clearMenu
=
function
()
{
let
selector
;
selector
=
'
.dropdown-content
'
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
if
(
this
.
options
.
containerSelector
)
{
selector
=
this
.
options
.
containerSelector
;
}
else
{
selector
=
'
.dropdown-page-one .dropdown-content
'
;
clearMenu
()
{
let
selector
=
'
.dropdown-content
'
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
if
(
this
.
options
.
containerSelector
)
{
selector
=
this
.
options
.
containerSelector
;
}
else
{
selector
=
'
.dropdown-page-one .dropdown-content
'
;
}
}
return
$
(
selector
,
this
.
dropdown
).
empty
();
}
re
turn
$
(
selector
,
this
.
dropdown
).
empty
();
}
;
re
nderItem
(
data
,
group
,
index
)
{
let
parent
;
GitLabDropdown
.
prototype
.
renderItem
=
function
(
data
,
group
,
index
)
{
let
parent
;
if
(
this
.
dropdown
&&
this
.
dropdown
[
0
])
{
parent
=
this
.
dropdown
[
0
].
parentNode
;
}
return
renderItem
({
instance
:
this
,
options
:
Object
.
assign
({},
this
.
options
,
{
icon
:
this
.
icon
,
highlight
:
this
.
highlight
,
highlightText
:
text
=>
this
.
highlightTextMatches
(
text
,
this
.
filterInput
.
val
()),
highlightTemplate
:
this
.
highlightTemplate
.
bind
(
this
),
parent
,
}),
data
,
group
,
index
,
});
};
if
(
this
.
dropdown
&&
this
.
dropdown
[
0
])
{
parent
=
this
.
dropdown
[
0
].
parentNode
;
}
GitLabDropdown
.
prototype
.
highlightTemplate
=
function
(
text
,
template
)
{
return
`"<b>
${
_
.
escape
(
text
)}
</b>"
${
template
}
`
;
};
return
renderItem
({
instance
:
this
,
options
:
Object
.
assign
({},
this
.
options
,
{
icon
:
this
.
icon
,
highlight
:
this
.
highlight
,
highlightText
:
text
=>
this
.
highlightTextMatches
(
text
,
this
.
filterInput
.
val
()),
highlightTemplate
:
this
.
highlightTemplate
.
bind
(
this
),
parent
,
}),
data
,
group
,
index
,
});
}
GitLabDropdown
.
prototype
.
highlightTextMatches
=
function
(
text
,
term
)
{
const
occurrences
=
fuzzaldrinPlus
.
match
(
text
,
term
);
const
{
indexOf
}
=
[];
// eslint-disable-next-line class-methods-use-this
highlightTemplate
(
text
,
template
)
{
return
`"<b>
${
_
.
escape
(
text
)}
</b>"
${
template
}
`
;
}
return
text
.
split
(
''
)
.
map
((
character
,
i
)
=>
{
if
(
indexOf
.
call
(
occurrences
,
i
)
!==
-
1
)
{
return
`<b>
${
character
}
</b>`
;
}
else
{
// eslint-disable-next-line class-methods-use-this
highlightTextMatches
(
text
,
term
)
{
const
occurrences
=
fuzzaldrinPlus
.
match
(
text
,
term
);
const
{
indexOf
}
=
[];
return
text
.
split
(
''
)
.
map
((
character
,
i
)
=>
{
if
(
indexOf
.
call
(
occurrences
,
i
)
!==
-
1
)
{
return
`<b>
${
character
}
</b>`
;
}
return
character
;
})
.
join
(
''
);
}
// eslint-disable-next-line class-methods-use-this
noResults
()
{
return
'
<li class="dropdown-menu-empty-item"><a>No matching results</a></li>
'
;
}
rowClicked
(
el
)
{
let
field
,
groupName
,
selectedIndex
,
selectedObject
,
isMarking
;
const
{
fieldName
}
=
this
.
options
;
const
isInput
=
$
(
this
.
el
).
is
(
'
input
'
);
if
(
this
.
renderedData
)
{
groupName
=
el
.
data
(
'
group
'
);
if
(
groupName
)
{
selectedIndex
=
el
.
data
(
'
index
'
);
selectedObject
=
this
.
renderedData
[
groupName
][
selectedIndex
];
}
else
{
selectedIndex
=
el
.
closest
(
'
li
'
).
index
();
this
.
selectedIndex
=
selectedIndex
;
selectedObject
=
this
.
renderedData
[
selectedIndex
];
}
})
.
join
(
''
);
};
}
GitLabDropdown
.
prototype
.
noResults
=
function
()
{
return
'
<li class="dropdown-menu-empty-item"><a>No matching results</a></li>
'
;
};
if
(
this
.
options
.
vue
)
{
if
(
el
.
hasClass
(
ACTIVE_CLASS
))
{
el
.
removeClass
(
ACTIVE_CLASS
);
}
else
{
el
.
addClass
(
ACTIVE_CLASS
);
}
GitLabDropdown
.
prototype
.
rowClicked
=
function
(
el
)
{
let
field
,
groupName
,
selectedIndex
,
selectedObject
,
isMarking
;
const
{
fieldName
}
=
this
.
options
;
const
isInput
=
$
(
this
.
el
).
is
(
'
input
'
);
if
(
this
.
renderedData
)
{
groupName
=
el
.
data
(
'
group
'
);
if
(
groupName
)
{
selectedIndex
=
el
.
data
(
'
index
'
);
selectedObject
=
this
.
renderedData
[
groupName
][
selectedIndex
];
}
else
{
selectedIndex
=
el
.
closest
(
'
li
'
).
index
();
this
.
selectedIndex
=
selectedIndex
;
selectedObject
=
this
.
renderedData
[
selectedIndex
];
return
[
selectedObject
];
}
}
if
(
this
.
options
.
vue
)
{
if
(
el
.
hasClass
(
ACTIVE_CLASS
))
{
el
.
removeClass
(
ACTIVE_CLASS
);
}
else
{
el
.
addClass
(
ACTIVE_CLASS
);
field
=
[];
const
value
=
this
.
options
.
id
?
this
.
options
.
id
(
selectedObject
,
el
)
:
selectedObject
.
id
;
if
(
isInput
)
{
field
=
$
(
this
.
el
);
}
else
if
(
value
!=
null
)
{
field
=
this
.
dropdown
.
parent
()
.
find
(
`input[name='
${
fieldName
}
'][value='
${
value
.
toString
().
replace
(
/'/g
,
"
\\
'
"
)}
']`
);
}
return
[
selectedObject
];
}
if
(
this
.
options
.
isSelectable
&&
!
this
.
options
.
isSelectable
(
selectedObject
,
el
))
{
return
[
selectedObject
];
}
field
=
[];
const
value
=
this
.
options
.
id
?
this
.
options
.
id
(
selectedObject
,
el
)
:
selectedObject
.
id
;
if
(
isInput
)
{
field
=
$
(
this
.
el
);
}
else
if
(
value
!=
null
)
{
field
=
this
.
dropdown
.
parent
()
.
find
(
`input[name='
${
fieldName
}
'][value='
${
value
.
toString
().
replace
(
/'/g
,
"
\\
'
"
)}
']`
);
}
if
(
this
.
options
.
isSelectable
&&
!
this
.
options
.
isSelectable
(
selectedObject
,
el
))
{
return
[
selectedObject
];
}
if
(
el
.
hasClass
(
ACTIVE_CLASS
)
&&
value
!==
0
)
{
isMarking
=
false
;
el
.
removeClass
(
ACTIVE_CLASS
);
if
(
field
&&
field
.
length
)
{
this
.
clearField
(
field
,
isInput
);
}
}
else
if
(
el
.
hasClass
(
INDETERMINATE_CLASS
))
{
isMarking
=
true
;
el
.
addClass
(
ACTIVE_CLASS
);
el
.
removeClass
(
INDETERMINATE_CLASS
);
if
(
field
&&
field
.
length
&&
value
==
null
)
{
this
.
clearField
(
field
,
isInput
);
}
if
((
!
field
||
!
field
.
length
)
&&
fieldName
)
{
this
.
addInput
(
fieldName
,
value
,
selectedObject
);
}
}
else
{
isMarking
=
true
;
if
(
!
this
.
options
.
multiSelect
||
el
.
hasClass
(
'
dropdown-clear-active
'
))
{
this
.
dropdown
.
find
(
`.
${
ACTIVE_CLASS
}
`
).
removeClass
(
ACTIVE_CLASS
);
if
(
!
isInput
)
{
this
.
dropdown
.
parent
()
.
find
(
`input[name='
${
fieldName
}
']`
)
.
remove
();
if
(
el
.
hasClass
(
ACTIVE_CLASS
)
&&
value
!==
0
)
{
isMarking
=
false
;
el
.
removeClass
(
ACTIVE_CLASS
);
if
(
field
&&
field
.
length
)
{
this
.
clearField
(
field
,
isInput
);
}
}
else
if
(
el
.
hasClass
(
INDETERMINATE_CLASS
))
{
isMarking
=
true
;
el
.
addClass
(
ACTIVE_CLASS
);
el
.
removeClass
(
INDETERMINATE_CLASS
);
if
(
field
&&
field
.
length
&&
value
==
null
)
{
this
.
clearField
(
field
,
isInput
);
}
}
if
(
field
&&
field
.
length
&&
value
==
null
)
{
this
.
clearField
(
field
,
isInput
);
}
// Toggle active class for the tick mark
el
.
addClass
(
ACTIVE_CLASS
);
if
(
value
!=
null
)
{
if
((
!
field
||
!
field
.
length
)
&&
fieldName
)
{
this
.
addInput
(
fieldName
,
value
,
selectedObject
);
}
else
if
(
field
&&
field
.
length
)
{
field
.
val
(
value
).
trigger
(
'
change
'
);
}
}
else
{
isMarking
=
true
;
if
(
!
this
.
options
.
multiSelect
||
el
.
hasClass
(
'
dropdown-clear-active
'
))
{
this
.
dropdown
.
find
(
`.
${
ACTIVE_CLASS
}
`
).
removeClass
(
ACTIVE_CLASS
);
if
(
!
isInput
)
{
this
.
dropdown
.
parent
()
.
find
(
`input[name='
${
fieldName
}
']`
)
.
remove
();
}
}
if
(
field
&&
field
.
length
&&
value
==
null
)
{
this
.
clearField
(
field
,
isInput
);
}
// Toggle active class for the tick mark
el
.
addClass
(
ACTIVE_CLASS
);
if
(
value
!=
null
)
{
if
((
!
field
||
!
field
.
length
)
&&
fieldName
)
{
this
.
addInput
(
fieldName
,
value
,
selectedObject
);
}
else
if
(
field
&&
field
.
length
)
{
field
.
val
(
value
).
trigger
(
'
change
'
);
}
}
}
return
[
selectedObject
,
isMarking
];
}
return
[
selectedObject
,
isMarking
];
};
focusTextInput
()
{
if
(
this
.
options
.
filterable
)
{
const
initialScrollTop
=
$
(
window
).
scrollTop
();
GitLabDropdown
.
prototype
.
focusTextInput
=
function
(
)
{
if
(
this
.
options
.
filterable
)
{
const
initialScrollTop
=
$
(
window
).
scrollTop
();
if
(
this
.
dropdown
.
is
(
'
.show
'
)
&&
!
this
.
filterInput
.
is
(
'
:focus
'
)
)
{
this
.
filterInput
.
focus
();
}
if
(
this
.
dropdown
.
is
(
'
.show
'
)
&&
!
this
.
filterInput
.
is
(
'
:focus
'
))
{
this
.
filterInput
.
focus
();
if
(
$
(
window
).
scrollTop
()
<
initialScrollTop
)
{
$
(
window
).
scrollTop
(
initialScrollTop
);
}
}
}
if
(
$
(
window
).
scrollTop
()
<
initialScrollTop
)
{
$
(
window
).
scrollTop
(
initialScrollTop
);
addInput
(
fieldName
,
value
,
selectedObject
,
single
)
{
// Create hidden input for form
if
(
single
)
{
$
(
`input[name="
${
fieldName
}
"]`
).
remove
();
}
}
};
GitLabDropdown
.
prototype
.
addInput
=
function
(
fieldName
,
value
,
selectedObject
,
single
)
{
// Create hidden input for form
if
(
single
)
{
$
(
`input[name="
${
fieldName
}
"]`
).
remove
();
}
const
$input
=
$
(
'
<input>
'
)
.
attr
(
'
type
'
,
'
hidden
'
)
.
attr
(
'
name
'
,
fieldName
)
.
val
(
value
);
if
(
this
.
options
.
inputId
!=
null
)
{
$input
.
attr
(
'
id
'
,
this
.
options
.
inputId
);
}
const
$input
=
$
(
'
<input>
'
)
.
attr
(
'
type
'
,
'
hidden
'
)
.
attr
(
'
name
'
,
fieldName
)
.
val
(
value
);
if
(
this
.
options
.
inputId
!=
null
)
{
$input
.
attr
(
'
id
'
,
this
.
options
.
inputId
);
}
if
(
this
.
options
.
multiSelect
)
{
Object
.
keys
(
selectedObject
).
forEach
(
attribute
=>
{
$input
.
attr
(
`data-
${
attribute
}
`
,
selectedObject
[
attribute
]);
});
}
if
(
this
.
options
.
multiSelect
)
{
Object
.
keys
(
selectedObject
).
forEach
(
attribute
=>
{
$input
.
attr
(
`data-
${
attribute
}
`
,
selectedObject
[
attribute
]);
});
}
if
(
this
.
options
.
inputMeta
)
{
$input
.
attr
(
'
data-meta
'
,
selectedObject
[
this
.
options
.
inputMeta
]);
}
if
(
this
.
options
.
inputMeta
)
{
$input
.
attr
(
'
data-meta
'
,
selectedObject
[
this
.
options
.
inputMeta
]);
this
.
dropdown
.
before
(
$input
).
trigger
(
'
change
'
);
}
this
.
dropdown
.
before
(
$input
).
trigger
(
'
change
'
);
};
GitLabDropdown
.
prototype
.
selectRowAtIndex
=
function
(
index
)
{
let
selector
;
// If we pass an option index
if
(
typeof
index
!==
'
undefined
'
)
{
selector
=
`
${
SELECTABLE_CLASSES
}
:eq(
${
index
}
) a`
;
}
else
{
selector
=
'
.dropdown-content .is-focused
'
;
}
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
// simulate a click on the first link
const
$el
=
$
(
selector
,
this
.
dropdown
);
if
(
$el
.
length
)
{
const
href
=
$el
.
attr
(
'
href
'
);
if
(
href
&&
href
!==
'
#
'
)
{
visitUrl
(
href
);
selectRowAtIndex
(
index
)
{
// If we pass an option index
let
selector
;
if
(
typeof
index
!==
'
undefined
'
)
{
selector
=
`
${
SELECTABLE_CLASSES
}
:eq(
${
index
}
) a`
;
}
else
{
$el
.
trigger
(
'
click
'
);
selector
=
'
.dropdown-content .is-focused
'
;
}
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
// simulate a click on the first link
const
$el
=
$
(
selector
,
this
.
dropdown
);
if
(
$el
.
length
)
{
const
href
=
$el
.
attr
(
'
href
'
);
if
(
href
&&
href
!==
'
#
'
)
{
visitUrl
(
href
);
}
else
{
$el
.
trigger
(
'
click
'
);
}
}
}
};
GitLabDropdown
.
prototype
.
addArrowKeyEvent
=
function
()
{
let
selector
;
const
ARROW_KEY_CODES
=
[
38
,
40
];
selector
=
SELECTABLE_CLASSES
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
return
$
(
'
body
'
).
on
(
'
keydown
'
,
e
=>
{
let
$listItems
,
PREV_INDEX
;
const
currentKeyCode
=
e
.
which
;
if
(
ARROW_KEY_CODES
.
indexOf
(
currentKeyCode
)
!==
-
1
)
{
e
.
preventDefault
();
e
.
stopImmediatePropagation
();
PREV_INDEX
=
currentIndex
;
$listItems
=
$
(
selector
,
this
.
dropdown
);
// if @options.filterable
// $input.blur()
if
(
currentKeyCode
===
40
)
{
// Move down
if
(
currentIndex
<
$listItems
.
length
-
1
)
{
currentIndex
+=
1
;
addArrowKeyEvent
()
{
const
ARROW_KEY_CODES
=
[
38
,
40
];
let
selector
=
SELECTABLE_CLASSES
;
if
(
this
.
dropdown
.
find
(
'
.dropdown-toggle-page
'
).
length
)
{
selector
=
`.dropdown-page-one
${
selector
}
`
;
}
return
$
(
'
body
'
).
on
(
'
keydown
'
,
e
=>
{
let
$listItems
,
PREV_INDEX
;
const
currentKeyCode
=
e
.
which
;
if
(
ARROW_KEY_CODES
.
indexOf
(
currentKeyCode
)
!==
-
1
)
{
e
.
preventDefault
();
e
.
stopImmediatePropagation
();
PREV_INDEX
=
currentIndex
;
$listItems
=
$
(
selector
,
this
.
dropdown
);
// if @options.filterable
// $input.blur()
if
(
currentKeyCode
===
40
)
{
// Move down
if
(
currentIndex
<
$listItems
.
length
-
1
)
{
currentIndex
+=
1
;
}
}
else
if
(
currentKeyCode
===
38
)
{
// Move up
if
(
currentIndex
>
0
)
{
currentIndex
-=
1
;
}
}
}
else
if
(
currentKeyCode
===
38
)
{
// Move up
if
(
currentIndex
>
0
)
{
currentIndex
-=
1
;
if
(
currentIndex
!==
PREV_INDEX
)
{
this
.
highlightRowAtIndex
(
$listItems
,
currentIndex
);
}
return
false
;
}
if
(
currentIndex
!==
PREV_INDEX
)
{
this
.
highlightRowAtIndex
(
$listItems
,
currentIndex
);
if
(
currentKeyCode
===
13
&&
currentIndex
!==
-
1
)
{
e
.
preventDefault
();
this
.
selectRowAtIndex
();
}
return
false
;
}
if
(
currentKeyCode
===
13
&&
currentIndex
!==
-
1
)
{
e
.
preventDefault
();
this
.
selectRowAtIndex
();
}
});
};
GitLabDropdown
.
prototype
.
removeArrowKeyEvent
=
function
()
{
return
$
(
'
body
'
).
off
(
'
keydown
'
);
};
GitLabDropdown
.
prototype
.
resetRows
=
function
resetRows
()
{
currentIndex
=
-
1
;
$
(
'
.is-focused
'
,
this
.
dropdown
).
removeClass
(
'
is-focused
'
);
};
GitLabDropdown
.
prototype
.
highlightRowAtIndex
=
function
(
$listItems
,
index
)
{
if
(
!
$listItems
)
{
$listItems
=
$
(
SELECTABLE_CLASSES
,
this
.
dropdown
);
}
// Remove the class for the previously focused row
$
(
'
.is-focused
'
,
this
.
dropdown
).
removeClass
(
'
is-focused
'
);
// Update the class for the row at the specific index
const
$listItem
=
$listItems
.
eq
(
index
);
$listItem
.
find
(
'
a:first-child
'
).
addClass
(
'
is-focused
'
);
// Dropdown content scroll area
const
$dropdownContent
=
$listItem
.
closest
(
'
.dropdown-content
'
);
const
dropdownScrollTop
=
$dropdownContent
.
scrollTop
();
const
dropdownContentHeight
=
$dropdownContent
.
outerHeight
();
const
dropdownContentTop
=
$dropdownContent
.
prop
(
'
offsetTop
'
);
const
dropdownContentBottom
=
dropdownContentTop
+
dropdownContentHeight
;
// Get the offset bottom of the list item
const
listItemHeight
=
$listItem
.
outerHeight
();
const
listItemTop
=
$listItem
.
prop
(
'
offsetTop
'
);
const
listItemBottom
=
listItemTop
+
listItemHeight
;
if
(
!
index
)
{
// Scroll the dropdown content to the top
$dropdownContent
.
scrollTop
(
0
);
}
else
if
(
index
===
$listItems
.
length
-
1
)
{
// Scroll the dropdown content to the bottom
$dropdownContent
.
scrollTop
(
$dropdownContent
.
prop
(
'
scrollHeight
'
));
}
else
if
(
listItemBottom
>
dropdownContentBottom
+
dropdownScrollTop
)
{
// Scroll the dropdown content down
$dropdownContent
.
scrollTop
(
listItemBottom
-
dropdownContentBottom
+
CURSOR_SELECT_SCROLL_PADDING
,
);
}
else
if
(
listItemTop
<
dropdownContentTop
+
dropdownScrollTop
)
{
// Scroll the dropdown content up
return
$dropdownContent
.
scrollTop
(
listItemTop
-
dropdownContentTop
-
CURSOR_SELECT_SCROLL_PADDING
,
);
});
}
};
GitLabDropdown
.
prototype
.
updateLabel
=
function
(
selected
,
el
,
instance
)
{
if
(
selected
==
null
)
{
selected
=
null
;
// eslint-disable-next-line class-methods-use-this
removeArrowKeyEvent
(
)
{
return
$
(
'
body
'
).
off
(
'
keydown
'
)
;
}
if
(
el
==
null
)
{
el
=
null
;
}
if
(
instance
==
null
)
{
instance
=
null
;
resetRows
()
{
currentIndex
=
-
1
;
$
(
'
.is-focused
'
,
this
.
dropdown
).
removeClass
(
'
is-focused
'
);
}
let
toggleText
=
this
.
options
.
toggleLabel
(
selected
,
el
,
instance
);
if
(
this
.
options
.
updateLabel
)
{
// Option to override the dropdown label text
toggleText
=
this
.
options
.
updateLabel
;
highlightRowAtIndex
(
$listItems
,
index
)
{
if
(
!
$listItems
)
{
// eslint-disable-next-line no-param-reassign
$listItems
=
$
(
SELECTABLE_CLASSES
,
this
.
dropdown
);
}
// Remove the class for the previously focused row
$
(
'
.is-focused
'
,
this
.
dropdown
).
removeClass
(
'
is-focused
'
);
// Update the class for the row at the specific index
const
$listItem
=
$listItems
.
eq
(
index
);
$listItem
.
find
(
'
a:first-child
'
).
addClass
(
'
is-focused
'
);
// Dropdown content scroll area
const
$dropdownContent
=
$listItem
.
closest
(
'
.dropdown-content
'
);
const
dropdownScrollTop
=
$dropdownContent
.
scrollTop
();
const
dropdownContentHeight
=
$dropdownContent
.
outerHeight
();
const
dropdownContentTop
=
$dropdownContent
.
prop
(
'
offsetTop
'
);
const
dropdownContentBottom
=
dropdownContentTop
+
dropdownContentHeight
;
// Get the offset bottom of the list item
const
listItemHeight
=
$listItem
.
outerHeight
();
const
listItemTop
=
$listItem
.
prop
(
'
offsetTop
'
);
const
listItemBottom
=
listItemTop
+
listItemHeight
;
if
(
!
index
)
{
// Scroll the dropdown content to the top
$dropdownContent
.
scrollTop
(
0
);
}
else
if
(
index
===
$listItems
.
length
-
1
)
{
// Scroll the dropdown content to the bottom
$dropdownContent
.
scrollTop
(
$dropdownContent
.
prop
(
'
scrollHeight
'
));
}
else
if
(
listItemBottom
>
dropdownContentBottom
+
dropdownScrollTop
)
{
// Scroll the dropdown content down
$dropdownContent
.
scrollTop
(
listItemBottom
-
dropdownContentBottom
+
CURSOR_SELECT_SCROLL_PADDING
,
);
}
else
if
(
listItemTop
<
dropdownContentTop
+
dropdownScrollTop
)
{
// Scroll the dropdown content up
return
$dropdownContent
.
scrollTop
(
listItemTop
-
dropdownContentTop
-
CURSOR_SELECT_SCROLL_PADDING
,
);
}
}
return
$
(
this
.
el
)
.
find
(
'
.dropdown-toggle-text
'
)
.
text
(
toggleText
);
};
updateLabel
(
selected
=
null
,
el
=
null
,
instance
=
null
)
{
let
toggleText
=
this
.
options
.
toggleLabel
(
selected
,
el
,
instance
);
if
(
this
.
options
.
updateLabel
)
{
// Option to override the dropdown label text
toggleText
=
this
.
options
.
updateLabel
;
}
GitLabDropdown
.
prototype
.
clearField
=
function
(
field
,
isInput
)
{
return
isInput
?
field
.
val
(
''
)
:
field
.
remove
();
};
return
$
(
this
.
el
)
.
find
(
'
.dropdown-toggle-text
'
)
.
text
(
toggleText
);
}
// eslint-disable-next-line class-methods-use-this
clearField
(
field
,
isInput
)
{
return
isInput
?
field
.
val
(
''
)
:
field
.
remove
();
}
}
// eslint-disable-next-line func-names
$
.
fn
.
glDropdown
=
function
(
opts
)
{
// eslint-disable-next-line func-names
return
this
.
each
(
function
()
{
if
(
!
$
.
data
(
this
,
'
glDropdown
'
))
{
return
$
.
data
(
this
,
'
glDropdown
'
,
new
GitLabDropdown
(
this
,
opts
));
...
...
changelogs/unreleased/Refactor-gl_dropdown-js.yml
0 → 100644
View file @
79f0044d
---
title
:
refactoring gl_dropdown.js to use ES6 classes instead of constructor functions
merge_request
:
20488
author
:
nuwe1
type
:
other
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment