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
ec9f617f
Commit
ec9f617f
authored
Sep 24, 2019
by
Lukas Eipert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Split gl_dropdown prototypes into multiple files
In this MR we simply split the gl_dropdown into multiple files.
parent
cf8b5288
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
911 additions
and
890 deletions
+911
-890
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js
...ets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js
+689
-0
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
...ascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
+135
-0
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_input.js
...vascripts/deprecated_jquery_dropdown/gl_dropdown_input.js
+44
-0
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_remote.js
...ascripts/deprecated_jquery_dropdown/gl_dropdown_remote.js
+42
-0
app/assets/javascripts/deprecated_jquery_dropdown/index.js
app/assets/javascripts/deprecated_jquery_dropdown/index.js
+1
-890
No files found.
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown.js
0 → 100644
View file @
ec9f617f
/* eslint-disable consistent-return */
import
$
from
'
jquery
'
;
import
{
escape
}
from
'
lodash
'
;
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
isObject
}
from
'
~/lib/utils/type_utility
'
;
import
renderItem
from
'
./render
'
;
import
{
GitLabDropdownRemote
}
from
'
./gl_dropdown_remote
'
;
import
{
GitLabDropdownInput
}
from
'
./gl_dropdown_input
'
;
import
{
GitLabDropdownFilter
}
from
'
./gl_dropdown_filter
'
;
const
LOADING_CLASS
=
'
is-loading
'
;
const
PAGE_TWO_CLASS
=
'
is-page-two
'
;
const
ACTIVE_CLASS
=
'
is-active
'
;
const
INDETERMINATE_CLASS
=
'
is-indeterminate
'
;
let
currentIndex
=
-
1
;
const
NON_SELECTABLE_CLASSES
=
'
.divider, .separator, .dropdown-header, .dropdown-menu-empty-item
'
;
const
SELECTABLE_CLASSES
=
`.dropdown-content li:not(
${
NON_SELECTABLE_CLASSES
}
, .option-hidden)`
;
const
CURSOR_SELECT_SCROLL_PADDING
=
5
;
const
FILTER_INPUT
=
'
.dropdown-input .dropdown-input-field:not(.dropdown-no-filter)
'
;
const
NO_FILTER_INPUT
=
'
.dropdown-input .dropdown-input-field.dropdown-no-filter
'
;
export
class
GitLabDropdown
{
constructor
(
el1
,
options
)
{
let
selector
;
let
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
(
typeof
this
.
filterInput
===
'
string
'
)
{
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
(
typeof
this
.
options
.
data
===
'
object
'
&&
!
(
this
.
options
.
data
instanceof
Function
))
{
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
.
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
;
let
$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
,
});
}
// Update label right after all modifications in dropdown has been done
if
(
this
.
options
.
toggleLabel
)
{
this
.
updateLabel
(
selectedObj
,
$el
,
this
);
}
$el
.
trigger
(
'
blur
'
);
});
}
}
// Finds an element inside wrapper element
getElement
(
selector
)
{
return
this
.
dropdown
.
find
(
selector
);
}
toggleLoading
()
{
return
$
(
'
.dropdown-menu
'
,
this
.
dropdown
).
toggleClass
(
LOADING_CLASS
);
}
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
();
}
parseData
(
data
)
{
let
groupData
;
let
html
;
this
.
renderedData
=
data
;
if
(
this
.
options
.
filterable
&&
data
.
length
===
0
)
{
// render no matching results
html
=
[
this
.
noResults
()];
}
// Handle array groups
else
if
(
isObject
(
data
))
{
html
=
[];
Object
.
keys
(
data
).
forEach
(
name
=>
{
groupData
=
data
[
name
];
html
.
push
(
this
.
renderItem
(
{
content
:
name
,
type
:
'
header
'
,
},
name
,
),
);
this
.
renderData
(
groupData
,
name
).
map
(
item
=>
html
.
push
(
item
));
});
}
else
{
// Render each row
html
=
this
.
renderData
(
data
);
}
// Render the full menu
const
fullHtml
=
this
.
renderMenu
(
html
);
return
this
.
appendMenu
(
fullHtml
);
}
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
;
}
}
filteredFullData
()
{
return
this
.
fullData
.
filter
(
r
=>
typeof
r
===
'
object
'
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
beforeDivider
'
)
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
header
'
),
);
}
opened
(
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
),
);
}
const
contentHtml
=
$
(
'
.dropdown-content
'
,
this
.
dropdown
).
html
();
if
(
this
.
remote
&&
contentHtml
===
''
)
{
this
.
remote
.
execute
();
}
else
{
this
.
focusTextInput
();
}
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
);
}
}
return
this
.
dropdown
.
trigger
(
'
shown.gl.dropdown
'
);
}
positionMenuAbove
()
{
const
$menu
=
this
.
dropdown
.
find
(
'
.dropdown-menu
'
);
$menu
.
addClass
(
'
dropdown-open-top
'
);
$menu
.
css
(
'
top
'
,
'
initial
'
);
$menu
.
css
(
'
bottom
'
,
'
100%
'
);
}
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
'
);
}
// 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
appendMenu
(
html
)
{
return
this
.
clearMenu
().
append
(
html
);
}
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
();
}
renderItem
(
data
,
group
,
index
)
{
let
parent
;
if
(
this
.
dropdown
&&
this
.
dropdown
[
0
])
{
parent
=
this
.
dropdown
[
0
].
parentNode
;
}
return
renderItem
({
instance
:
this
,
options
:
{
...
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
,
});
}
// eslint-disable-next-line class-methods-use-this
highlightTemplate
(
text
,
template
)
{
return
`"<b>
${
escape
(
text
)}
</b>"
${
template
}
`
;
}
// 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
;
let
groupName
;
let
selectedIndex
;
let
selectedObject
;
let
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
];
}
}
if
(
this
.
options
.
vue
)
{
if
(
el
.
hasClass
(
ACTIVE_CLASS
))
{
el
.
removeClass
(
ACTIVE_CLASS
);
}
else
{
el
.
addClass
(
ACTIVE_CLASS
);
}
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
(
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
];
}
focusTextInput
()
{
if
(
this
.
options
.
filterable
)
{
const
initialScrollTop
=
$
(
window
).
scrollTop
();
if
(
this
.
dropdown
.
is
(
'
.show
'
)
&&
!
this
.
filterInput
.
is
(
'
:focus
'
))
{
this
.
filterInput
.
focus
();
}
if
(
$
(
window
).
scrollTop
()
<
initialScrollTop
)
{
$
(
window
).
scrollTop
(
initialScrollTop
);
}
}
}
addInput
(
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
);
}
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
]);
}
this
.
dropdown
.
before
(
$input
).
trigger
(
'
change
'
);
}
selectRowAtIndex
(
index
)
{
// If we pass an option index
let
selector
;
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
);
}
else
{
$el
.
trigger
(
'
click
'
);
}
}
}
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
;
let
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
;
}
}
if
(
currentIndex
!==
PREV_INDEX
)
{
this
.
highlightRowAtIndex
(
$listItems
,
currentIndex
);
}
return
false
;
}
if
(
currentKeyCode
===
13
&&
currentIndex
!==
-
1
)
{
e
.
preventDefault
();
this
.
selectRowAtIndex
();
}
});
}
// eslint-disable-next-line class-methods-use-this
removeArrowKeyEvent
()
{
return
$
(
'
body
'
).
off
(
'
keydown
'
);
}
resetRows
()
{
currentIndex
=
-
1
;
$
(
'
.is-focused
'
,
this
.
dropdown
).
removeClass
(
'
is-focused
'
);
}
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
,
);
}
}
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
;
}
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
();
}
}
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
0 → 100644
View file @
ec9f617f
/* eslint-disable consistent-return */
import
$
from
'
jquery
'
;
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
import
{
isObject
}
from
'
~/lib/utils/type_utility
'
;
const
BLUR_KEYCODES
=
[
27
,
40
];
const
HAS_VALUE_CLASS
=
'
has-value
'
;
export
class
GitLabDropdownFilter
{
constructor
(
input
,
options
)
{
let
ref
;
let
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
());
});
}
static
shouldBlur
(
keyCode
)
{
return
BLUR_KEYCODES
.
indexOf
(
keyCode
)
!==
-
1
;
}
filter
(
searchText
)
{
let
group
;
let
results
;
let
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
(
Array
.
isArray
(
data
))
{
results
=
fuzzaldrinPlus
.
filter
(
data
,
searchText
,
{
key
:
this
.
options
.
keys
,
});
}
// If data is grouped therefore an [object Object]. e.g.
// {
// groupName1: [
// { prop: 'foo' },
// { prop: 'baz' }
// ],
// groupName2: [
// { prop: 'abc' },
// { prop: 'def' }
// ]
// }
else
if
(
isObject
(
data
))
{
results
=
{};
Object
.
keys
(
data
).
forEach
(
key
=>
{
group
=
data
[
key
];
tmp
=
fuzzaldrinPlus
.
filter
(
group
,
searchText
,
{
key
:
this
.
options
.
keys
,
});
if
(
tmp
.
length
)
{
results
[
key
]
=
tmp
.
map
(
item
=>
item
);
}
});
}
}
return
this
.
options
.
callback
(
results
);
}
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
(),
searchText
);
if
(
!
$el
.
is
(
'
.dropdown-header
'
))
{
if
(
matches
.
length
)
{
return
$el
.
show
().
removeClass
(
'
option-hidden
'
);
}
return
$el
.
hide
().
addClass
(
'
option-hidden
'
);
}
});
}
else
{
elements
.
show
().
removeClass
(
'
option-hidden
'
);
}
elements
.
parent
()
.
find
(
'
.dropdown-menu-empty-item
'
)
.
toggleClass
(
'
hidden
'
,
elements
.
is
(
'
:visible
'
));
}
}
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_input.js
0 → 100644
View file @
ec9f617f
export
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
();
});
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
);
});
}
onInput
(
cb
)
{
this
.
cb
=
cb
;
}
}
app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_remote.js
0 → 100644
View file @
ec9f617f
/* eslint-disable consistent-return */
import
axios
from
'
../lib/utils/axios_utils
'
;
export
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
();
}
// 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
);
}
});
}
}
app/assets/javascripts/deprecated_jquery_dropdown/index.js
View file @
ec9f617f
/* eslint-disable max-classes-per-file, one-var, consistent-return */
import
$
from
'
jquery
'
;
import
{
escape
}
from
'
lodash
'
;
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
import
axios
from
'
../lib/utils/axios_utils
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
isObject
}
from
'
~/lib/utils/type_utility
'
;
import
renderItem
from
'
./render
'
;
const
BLUR_KEYCODES
=
[
27
,
40
];
const
HAS_VALUE_CLASS
=
'
has-value
'
;
const
LOADING_CLASS
=
'
is-loading
'
;
const
PAGE_TWO_CLASS
=
'
is-page-two
'
;
const
ACTIVE_CLASS
=
'
is-active
'
;
const
INDETERMINATE_CLASS
=
'
is-indeterminate
'
;
let
currentIndex
=
-
1
;
const
NON_SELECTABLE_CLASSES
=
'
.divider, .separator, .dropdown-header, .dropdown-menu-empty-item
'
;
const
SELECTABLE_CLASSES
=
`.dropdown-content li:not(
${
NON_SELECTABLE_CLASSES
}
, .option-hidden)`
;
const
CURSOR_SELECT_SCROLL_PADDING
=
5
;
const
FILTER_INPUT
=
'
.dropdown-input .dropdown-input-field:not(.dropdown-no-filter)
'
;
const
NO_FILTER_INPUT
=
'
.dropdown-input .dropdown-input-field.dropdown-no-filter
'
;
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
();
});
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
);
});
}
onInput
(
cb
)
{
this
.
cb
=
cb
;
}
}
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
());
});
}
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
(
Array
.
isArray
(
data
))
{
results
=
fuzzaldrinPlus
.
filter
(
data
,
searchText
,
{
key
:
this
.
options
.
keys
,
});
}
// If data is grouped therefore an [object Object]. e.g.
// {
// groupName1: [
// { prop: 'foo' },
// { prop: 'baz' }
// ],
// groupName2: [
// { prop: 'abc' },
// { prop: 'def' }
// ]
// }
else
if
(
isObject
(
data
))
{
results
=
{};
Object
.
keys
(
data
).
forEach
(
key
=>
{
group
=
data
[
key
];
tmp
=
fuzzaldrinPlus
.
filter
(
group
,
searchText
,
{
key
:
this
.
options
.
keys
,
});
if
(
tmp
.
length
)
{
results
[
key
]
=
tmp
.
map
(
item
=>
item
);
}
});
}
}
return
this
.
options
.
callback
(
results
);
}
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
(),
searchText
);
if
(
!
$el
.
is
(
'
.dropdown-header
'
))
{
if
(
matches
.
length
)
{
return
$el
.
show
().
removeClass
(
'
option-hidden
'
);
}
return
$el
.
hide
().
addClass
(
'
option-hidden
'
);
}
});
}
else
{
elements
.
show
().
removeClass
(
'
option-hidden
'
);
}
elements
.
parent
()
.
find
(
'
.dropdown-menu-empty-item
'
)
.
toggleClass
(
'
hidden
'
,
elements
.
is
(
'
:visible
'
));
}
}
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
();
}
// 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
(
typeof
this
.
filterInput
===
'
string
'
)
{
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
(
typeof
this
.
options
.
data
===
'
object
'
&&
!
(
this
.
options
.
data
instanceof
Function
))
{
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
.
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
'
);
}
}
});
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
,
});
}
// Update label right after all modifications in dropdown has been done
if
(
this
.
options
.
toggleLabel
)
{
this
.
updateLabel
(
selectedObj
,
$el
,
this
);
}
$el
.
trigger
(
'
blur
'
);
});
}
}
// Finds an element inside wrapper element
getElement
(
selector
)
{
return
this
.
dropdown
.
find
(
selector
);
}
toggleLoading
()
{
return
$
(
'
.dropdown-menu
'
,
this
.
dropdown
).
toggleClass
(
LOADING_CLASS
);
}
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
();
}
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
else
if
(
isObject
(
data
))
{
html
=
[];
Object
.
keys
(
data
).
forEach
(
name
=>
{
groupData
=
data
[
name
];
html
.
push
(
this
.
renderItem
(
{
content
:
name
,
type
:
'
header
'
,
},
name
,
),
);
this
.
renderData
(
groupData
,
name
).
map
(
item
=>
html
.
push
(
item
));
});
}
else
{
// Render each row
html
=
this
.
renderData
(
data
);
}
// Render the full menu
const
fullHtml
=
this
.
renderMenu
(
html
);
return
this
.
appendMenu
(
fullHtml
);
}
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
;
}
}
filteredFullData
()
{
return
this
.
fullData
.
filter
(
r
=>
typeof
r
===
'
object
'
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
beforeDivider
'
)
&&
!
Object
.
prototype
.
hasOwnProperty
.
call
(
r
,
'
header
'
),
);
}
opened
(
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
),
);
}
const
contentHtml
=
$
(
'
.dropdown-content
'
,
this
.
dropdown
).
html
();
if
(
this
.
remote
&&
contentHtml
===
''
)
{
this
.
remote
.
execute
();
}
else
{
this
.
focusTextInput
();
}
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
);
}
}
return
this
.
dropdown
.
trigger
(
'
shown.gl.dropdown
'
);
}
positionMenuAbove
()
{
const
$menu
=
this
.
dropdown
.
find
(
'
.dropdown-menu
'
);
$menu
.
addClass
(
'
dropdown-open-top
'
);
$menu
.
css
(
'
top
'
,
'
initial
'
);
$menu
.
css
(
'
bottom
'
,
'
100%
'
);
}
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
'
);
}
// 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
appendMenu
(
html
)
{
return
this
.
clearMenu
().
append
(
html
);
}
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
();
}
renderItem
(
data
,
group
,
index
)
{
let
parent
;
if
(
this
.
dropdown
&&
this
.
dropdown
[
0
])
{
parent
=
this
.
dropdown
[
0
].
parentNode
;
}
return
renderItem
({
instance
:
this
,
options
:
{
...
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
,
});
}
// eslint-disable-next-line class-methods-use-this
highlightTemplate
(
text
,
template
)
{
return
`"<b>
${
escape
(
text
)}
</b>"
${
template
}
`
;
}
// 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
];
}
}
if
(
this
.
options
.
vue
)
{
if
(
el
.
hasClass
(
ACTIVE_CLASS
))
{
el
.
removeClass
(
ACTIVE_CLASS
);
}
else
{
el
.
addClass
(
ACTIVE_CLASS
);
}
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
(
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
];
}
focusTextInput
()
{
if
(
this
.
options
.
filterable
)
{
const
initialScrollTop
=
$
(
window
).
scrollTop
();
if
(
this
.
dropdown
.
is
(
'
.show
'
)
&&
!
this
.
filterInput
.
is
(
'
:focus
'
))
{
this
.
filterInput
.
focus
();
}
if
(
$
(
window
).
scrollTop
()
<
initialScrollTop
)
{
$
(
window
).
scrollTop
(
initialScrollTop
);
}
}
}
addInput
(
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
);
}
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
]);
}
this
.
dropdown
.
before
(
$input
).
trigger
(
'
change
'
);
}
selectRowAtIndex
(
index
)
{
// If we pass an option index
let
selector
;
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
);
}
else
{
$el
.
trigger
(
'
click
'
);
}
}
}
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
;
}
}
if
(
currentIndex
!==
PREV_INDEX
)
{
this
.
highlightRowAtIndex
(
$listItems
,
currentIndex
);
}
return
false
;
}
if
(
currentKeyCode
===
13
&&
currentIndex
!==
-
1
)
{
e
.
preventDefault
();
this
.
selectRowAtIndex
();
}
});
}
// eslint-disable-next-line class-methods-use-this
removeArrowKeyEvent
()
{
return
$
(
'
body
'
).
off
(
'
keydown
'
);
}
resetRows
()
{
currentIndex
=
-
1
;
$
(
'
.is-focused
'
,
this
.
dropdown
).
removeClass
(
'
is-focused
'
);
}
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
,
);
}
}
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
;
}
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
();
}
}
import
{
GitLabDropdown
}
from
'
./gl_dropdown
'
;
export
default
function
initDeprecatedJQueryDropdown
(
$el
,
opts
)
{
// eslint-disable-next-line func-names
...
...
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