2016-03-07 10:37:35 -05:00
|
|
|
class GitLabDropdownFilter
|
|
|
|
BLUR_KEYCODES = [27, 40]
|
|
|
|
|
2016-03-08 11:53:31 -05:00
|
|
|
constructor: (@dropdown, @options) ->
|
2016-03-08 06:23:54 -05:00
|
|
|
@input = @dropdown.find(".dropdown-input .dropdown-input-field")
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
# Key events
|
2016-03-08 04:09:39 -05:00
|
|
|
timeout = ""
|
2016-03-07 10:37:35 -05:00
|
|
|
@input.on "keyup", (e) =>
|
2016-03-08 11:53:31 -05:00
|
|
|
if e.keyCode is 13 && @input.val() isnt ""
|
|
|
|
if @options.enterCallback
|
|
|
|
@options.enterCallback()
|
|
|
|
return
|
|
|
|
|
2016-03-08 04:09:39 -05:00
|
|
|
clearTimeout timeout
|
|
|
|
timeout = setTimeout =>
|
|
|
|
blur_field = @shouldBlur e.keyCode
|
|
|
|
search_text = @input.val()
|
2016-03-07 10:37:35 -05:00
|
|
|
|
2016-03-08 04:09:39 -05:00
|
|
|
if blur_field
|
|
|
|
@input.blur()
|
2016-03-07 10:37:35 -05:00
|
|
|
|
2016-03-08 11:53:31 -05:00
|
|
|
if @options.remote
|
|
|
|
@options.query search_text, (data) =>
|
|
|
|
@options.callback(data)
|
2016-03-08 04:09:39 -05:00
|
|
|
else
|
|
|
|
@filter search_text
|
|
|
|
, 250
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
shouldBlur: (keyCode) ->
|
|
|
|
return BLUR_KEYCODES.indexOf(keyCode) >= 0
|
|
|
|
|
|
|
|
filter: (search_text) ->
|
2016-03-08 11:53:31 -05:00
|
|
|
data = @options.data()
|
2016-03-08 10:45:03 -05:00
|
|
|
results = data
|
|
|
|
|
|
|
|
if search_text isnt ""
|
|
|
|
results = fuzzaldrinPlus.filter(data, search_text,
|
2016-03-08 11:53:31 -05:00
|
|
|
key: @options.keys
|
2016-03-08 10:45:03 -05:00
|
|
|
)
|
2016-03-07 10:37:35 -05:00
|
|
|
|
2016-03-08 12:33:45 -05:00
|
|
|
@options.callback results
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
class GitLabDropdownRemote
|
|
|
|
constructor: (@dataEndpoint, @options) ->
|
|
|
|
|
|
|
|
execute: ->
|
|
|
|
if typeof @dataEndpoint is "string"
|
|
|
|
@fetchData()
|
|
|
|
else if typeof @dataEndpoint is "function"
|
|
|
|
if @options.beforeSend
|
|
|
|
@options.beforeSend()
|
|
|
|
|
|
|
|
# Fetch the data by calling the data funcfion
|
2016-03-08 04:09:39 -05:00
|
|
|
@dataEndpoint "", (data) =>
|
2016-03-07 10:37:35 -05:00
|
|
|
if @options.success
|
|
|
|
@options.success(data)
|
|
|
|
|
|
|
|
if @options.beforeSend
|
|
|
|
@options.beforeSend()
|
|
|
|
|
|
|
|
# Fetch the data through ajax if the data is a string
|
|
|
|
fetchData: ->
|
|
|
|
$.ajax(
|
|
|
|
url: @dataEndpoint,
|
|
|
|
dataType: @options.dataType,
|
|
|
|
beforeSend: =>
|
|
|
|
if @options.beforeSend
|
|
|
|
@options.beforeSend()
|
|
|
|
success: (data) =>
|
|
|
|
if @options.success
|
|
|
|
@options.success(data)
|
|
|
|
)
|
|
|
|
|
|
|
|
class GitLabDropdown
|
|
|
|
LOADING_CLASS = "is-loading"
|
2016-03-08 05:24:03 -05:00
|
|
|
PAGE_TWO_CLASS = "is-page-two"
|
2016-03-08 12:33:45 -05:00
|
|
|
ACTIVE_CLASS = "is-active"
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
constructor: (@el, @options) ->
|
|
|
|
self = @
|
|
|
|
@dropdown = $(@el).parent()
|
|
|
|
search_fields = if @options.search then @options.search.fields else [];
|
|
|
|
|
|
|
|
if @options.data
|
|
|
|
# Remote data
|
|
|
|
@remote = new GitLabDropdownRemote @options.data, {
|
|
|
|
dataType: @options.dataType,
|
|
|
|
beforeSend: @toggleLoading.bind(@)
|
|
|
|
success: (data) =>
|
|
|
|
@fullData = data
|
|
|
|
|
2016-03-08 10:45:03 -05:00
|
|
|
@parseData @fullData
|
2016-03-07 10:37:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
# Init filiterable
|
|
|
|
if @options.filterable
|
2016-03-08 11:53:31 -05:00
|
|
|
@filter = new GitLabDropdownFilter @dropdown,
|
|
|
|
remote: @options.filterRemote
|
|
|
|
query: @options.data
|
|
|
|
keys: @options.search.fields
|
|
|
|
data: =>
|
|
|
|
return @fullData
|
|
|
|
callback: (data) =>
|
|
|
|
@parseData data
|
|
|
|
enterCallback: =>
|
|
|
|
@selectFirstRow()
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
# Event listeners
|
2016-03-08 05:24:03 -05:00
|
|
|
@dropdown.on "shown.bs.dropdown", @opened
|
|
|
|
@dropdown.on "hidden.bs.dropdown", @hidden
|
|
|
|
|
|
|
|
if @dropdown.find(".dropdown-toggle-page").length
|
|
|
|
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
|
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
|
|
@togglePage()
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
if @options.selectable
|
2016-03-08 06:23:54 -05:00
|
|
|
selector = ".dropdown-content a"
|
2016-03-08 05:24:03 -05:00
|
|
|
|
|
|
|
if @dropdown.find(".dropdown-toggle-page").length
|
2016-03-08 06:23:54 -05:00
|
|
|
selector = ".dropdown-page-one .dropdown-content a"
|
2016-03-08 05:24:03 -05:00
|
|
|
|
|
|
|
@dropdown.on "click", selector, (e) ->
|
2016-03-07 10:37:35 -05:00
|
|
|
self.rowClicked $(@)
|
|
|
|
|
|
|
|
if self.options.clicked
|
|
|
|
self.options.clicked()
|
|
|
|
|
|
|
|
toggleLoading: ->
|
|
|
|
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
|
|
|
|
|
2016-03-08 05:24:03 -05:00
|
|
|
togglePage: ->
|
|
|
|
menu = $('.dropdown-menu', @dropdown)
|
|
|
|
|
|
|
|
if menu.hasClass(PAGE_TWO_CLASS)
|
|
|
|
if @remote
|
|
|
|
@remote.execute()
|
|
|
|
|
|
|
|
menu.toggleClass PAGE_TWO_CLASS
|
|
|
|
|
2016-03-07 10:37:35 -05:00
|
|
|
parseData: (data) ->
|
|
|
|
@renderedData = data
|
|
|
|
|
|
|
|
# Render each row
|
|
|
|
html = $.map data, (obj) =>
|
|
|
|
return @renderItem(obj)
|
|
|
|
|
|
|
|
if @options.filterable and data.length is 0
|
|
|
|
# render no matching results
|
|
|
|
html = [@noResults()]
|
|
|
|
|
|
|
|
# Render the full menu
|
|
|
|
full_html = @renderMenu(html.join(""))
|
|
|
|
|
|
|
|
@appendMenu(full_html)
|
|
|
|
|
|
|
|
opened: =>
|
2016-03-08 10:18:01 -05:00
|
|
|
contentHtml = $('.dropdown-content', @dropdown).html()
|
|
|
|
if @remote && contentHtml is ""
|
2016-03-07 10:37:35 -05:00
|
|
|
@remote.execute()
|
|
|
|
|
2016-03-08 04:09:39 -05:00
|
|
|
if @options.filterable
|
|
|
|
@dropdown.find(".dropdown-input-field").focus()
|
|
|
|
|
|
|
|
hidden: =>
|
|
|
|
if @options.filterable
|
|
|
|
@dropdown.find(".dropdown-input-field").blur().val("")
|
|
|
|
|
2016-03-08 05:24:03 -05:00
|
|
|
if @dropdown.find(".dropdown-toggle-page").length
|
|
|
|
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
|
|
|
|
|
|
|
|
|
2016-03-07 10:37:35 -05:00
|
|
|
# Render the full menu
|
|
|
|
renderMenu: (html) ->
|
|
|
|
menu_html = ""
|
|
|
|
|
|
|
|
if @options.renderMenu
|
|
|
|
menu_html = @options.renderMenu(html)
|
|
|
|
else
|
|
|
|
menu_html = "<ul>#{html}</ul>"
|
|
|
|
|
|
|
|
return menu_html
|
|
|
|
|
|
|
|
# Append the menu into the dropdown
|
|
|
|
appendMenu: (html) ->
|
2016-03-08 05:24:03 -05:00
|
|
|
selector = '.dropdown-content'
|
|
|
|
if @dropdown.find(".dropdown-toggle-page").length
|
|
|
|
selector = ".dropdown-page-one .dropdown-content"
|
|
|
|
|
|
|
|
$(selector, @dropdown).html html
|
2016-03-07 10:37:35 -05:00
|
|
|
|
|
|
|
# Render the row
|
|
|
|
renderItem: (data) ->
|
|
|
|
html = ""
|
|
|
|
|
2016-03-08 05:24:03 -05:00
|
|
|
return "<li class='divider'></li>" if data is "divider"
|
|
|
|
|
2016-03-07 10:37:35 -05:00
|
|
|
if @options.renderRow
|
|
|
|
# Call the render function
|
|
|
|
html = @options.renderRow(data)
|
|
|
|
else
|
|
|
|
selected = if @options.isSelected then @options.isSelected(data) else false
|
2016-03-08 03:38:16 -05:00
|
|
|
url = if @options.url then @options.url(data) else "#"
|
2016-03-07 10:37:35 -05:00
|
|
|
text = if @options.text then @options.text(data) else ""
|
|
|
|
cssClass = "";
|
|
|
|
|
|
|
|
if selected
|
|
|
|
cssClass = "is-active"
|
|
|
|
|
|
|
|
html = "<li>"
|
|
|
|
html += "<a href='#{url}' class='#{cssClass}'>"
|
|
|
|
html += text
|
|
|
|
html += "</a>"
|
|
|
|
html += "</li>"
|
|
|
|
|
|
|
|
return html
|
|
|
|
|
|
|
|
noResults: ->
|
|
|
|
html = "<li>"
|
|
|
|
html += "<a href='#' class='is-focused'>"
|
|
|
|
html += "No matching results."
|
|
|
|
html += "</a>"
|
|
|
|
html += "</li>"
|
|
|
|
|
|
|
|
rowClicked: (el) ->
|
|
|
|
fieldName = @options.fieldName
|
2016-03-08 12:33:45 -05:00
|
|
|
field = @dropdown.parent().find("input[name='#{fieldName}']")
|
2016-03-07 10:37:35 -05:00
|
|
|
|
2016-03-08 12:33:45 -05:00
|
|
|
if el.hasClass(ACTIVE_CLASS)
|
|
|
|
field.remove()
|
2016-03-07 10:37:35 -05:00
|
|
|
else
|
2016-03-08 12:33:45 -05:00
|
|
|
fieldName = @options.fieldName
|
|
|
|
selectedIndex = el.parent().index()
|
|
|
|
if @renderedData
|
|
|
|
selectedObject = @renderedData[selectedIndex]
|
|
|
|
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
|
|
|
|
|
2016-03-14 09:49:53 -04:00
|
|
|
if !value?
|
|
|
|
field.remove()
|
|
|
|
|
2016-03-08 12:33:45 -05:00
|
|
|
if @options.multiSelect
|
|
|
|
oldValue = field.val()
|
|
|
|
if oldValue
|
|
|
|
value = "#{oldValue},#{value}"
|
|
|
|
else
|
|
|
|
@dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS
|
|
|
|
|
|
|
|
# Toggle active class for the tick mark
|
|
|
|
el.toggleClass "is-active"
|
|
|
|
|
2016-03-14 16:04:05 -04:00
|
|
|
if value?
|
2016-03-08 12:33:45 -05:00
|
|
|
if !field.length
|
|
|
|
# Create hidden input for form
|
|
|
|
input = "<input type='hidden' name='#{fieldName}' />"
|
|
|
|
@dropdown.before input
|
|
|
|
|
|
|
|
@dropdown.parent().find("input[name='#{fieldName}']").val value
|
2016-03-07 10:37:35 -05:00
|
|
|
|
2016-03-08 11:53:31 -05:00
|
|
|
selectFirstRow: ->
|
|
|
|
selector = '.dropdown-content li:first-child a'
|
|
|
|
if @dropdown.find(".dropdown-toggle-page").length
|
|
|
|
selector = ".dropdown-page-one .dropdown-content li:first-child a"
|
|
|
|
|
|
|
|
# similute a click on the first link
|
|
|
|
$(selector).trigger "click"
|
|
|
|
|
2016-03-07 10:37:35 -05:00
|
|
|
$.fn.glDropdown = (opts) ->
|
|
|
|
return @.each ->
|
|
|
|
new GitLabDropdown @, opts
|