Merge branch 'dropdown-js' into 'master'
Dropdown JS See merge request !3112
This commit is contained in:
commit
e080028570
|
@ -4,6 +4,7 @@
|
|||
namespaces_path: "/api/:version/namespaces.json"
|
||||
group_projects_path: "/api/:version/groups/:id/projects.json"
|
||||
projects_path: "/api/:version/projects.json"
|
||||
labels_path: "/api/:version/projects/:id/labels"
|
||||
|
||||
group: (group_id, callback) ->
|
||||
url = Api.buildUrl(Api.group_path)
|
||||
|
@ -61,6 +62,19 @@
|
|||
).done (projects) ->
|
||||
callback(projects)
|
||||
|
||||
newLabel: (project_id, data, callback) ->
|
||||
url = Api.buildUrl(Api.labels_path)
|
||||
url = url.replace(':id', project_id)
|
||||
|
||||
data.private_token = gon.api_token
|
||||
$.ajax(
|
||||
url: url
|
||||
type: "POST"
|
||||
data: data
|
||||
dataType: "json"
|
||||
).done (label) ->
|
||||
callback(label)
|
||||
|
||||
# Return group projects list. Filtered by query
|
||||
groupProjects: (group_id, query, callback) ->
|
||||
url = Api.buildUrl(Api.group_projects_path)
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
class GitLabDropdownFilter
|
||||
BLUR_KEYCODES = [27, 40]
|
||||
|
||||
constructor: (@dropdown, @options) ->
|
||||
@input = @dropdown.find(".dropdown-input .dropdown-input-field")
|
||||
|
||||
# Key events
|
||||
timeout = ""
|
||||
@input.on "keyup", (e) =>
|
||||
if e.keyCode is 13 && @input.val() isnt ""
|
||||
if @options.enterCallback
|
||||
@options.enterCallback()
|
||||
return
|
||||
|
||||
clearTimeout timeout
|
||||
timeout = setTimeout =>
|
||||
blur_field = @shouldBlur e.keyCode
|
||||
search_text = @input.val()
|
||||
|
||||
if blur_field
|
||||
@input.blur()
|
||||
|
||||
if @options.remote
|
||||
@options.query search_text, (data) =>
|
||||
@options.callback(data)
|
||||
else
|
||||
@filter search_text
|
||||
, 250
|
||||
|
||||
shouldBlur: (keyCode) ->
|
||||
return BLUR_KEYCODES.indexOf(keyCode) >= 0
|
||||
|
||||
filter: (search_text) ->
|
||||
data = @options.data()
|
||||
results = data
|
||||
|
||||
if search_text isnt ""
|
||||
results = fuzzaldrinPlus.filter(data, search_text,
|
||||
key: @options.keys
|
||||
)
|
||||
|
||||
@options.callback results
|
||||
|
||||
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
|
||||
@dataEndpoint "", (data) =>
|
||||
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"
|
||||
PAGE_TWO_CLASS = "is-page-two"
|
||||
ACTIVE_CLASS = "is-active"
|
||||
|
||||
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
|
||||
|
||||
@parseData @fullData
|
||||
}
|
||||
|
||||
# Init filiterable
|
||||
if @options.filterable
|
||||
@filter = new GitLabDropdownFilter @dropdown,
|
||||
remote: @options.filterRemote
|
||||
query: @options.data
|
||||
keys: @options.search.fields
|
||||
data: =>
|
||||
return @fullData
|
||||
callback: (data) =>
|
||||
@parseData data
|
||||
enterCallback: =>
|
||||
@selectFirstRow()
|
||||
|
||||
# Event listeners
|
||||
@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()
|
||||
|
||||
if @options.selectable
|
||||
selector = ".dropdown-content a"
|
||||
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
selector = ".dropdown-page-one .dropdown-content a"
|
||||
|
||||
@dropdown.on "click", selector, (e) ->
|
||||
self.rowClicked $(@)
|
||||
|
||||
if self.options.clicked
|
||||
self.options.clicked()
|
||||
|
||||
toggleLoading: ->
|
||||
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
|
||||
|
||||
togglePage: ->
|
||||
menu = $('.dropdown-menu', @dropdown)
|
||||
|
||||
if menu.hasClass(PAGE_TWO_CLASS)
|
||||
if @remote
|
||||
@remote.execute()
|
||||
|
||||
menu.toggleClass PAGE_TWO_CLASS
|
||||
|
||||
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: =>
|
||||
contentHtml = $('.dropdown-content', @dropdown).html()
|
||||
if @remote && contentHtml is ""
|
||||
@remote.execute()
|
||||
|
||||
if @options.filterable
|
||||
@dropdown.find(".dropdown-input-field").focus()
|
||||
|
||||
hidden: =>
|
||||
if @options.filterable
|
||||
@dropdown.find(".dropdown-input-field").blur().val("")
|
||||
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
|
||||
|
||||
|
||||
# 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) ->
|
||||
selector = '.dropdown-content'
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
selector = ".dropdown-page-one .dropdown-content"
|
||||
|
||||
$(selector, @dropdown).html html
|
||||
|
||||
# Render the row
|
||||
renderItem: (data) ->
|
||||
html = ""
|
||||
|
||||
return "<li class='divider'></li>" if data is "divider"
|
||||
|
||||
if @options.renderRow
|
||||
# Call the render function
|
||||
html = @options.renderRow(data)
|
||||
else
|
||||
selected = if @options.isSelected then @options.isSelected(data) else false
|
||||
url = if @options.url then @options.url(data) else "#"
|
||||
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
|
||||
field = @dropdown.parent().find("input[name='#{fieldName}']")
|
||||
|
||||
if el.hasClass(ACTIVE_CLASS)
|
||||
field.remove()
|
||||
else
|
||||
fieldName = @options.fieldName
|
||||
selectedIndex = el.parent().index()
|
||||
if @renderedData
|
||||
selectedObject = @renderedData[selectedIndex]
|
||||
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
|
||||
|
||||
if @options.multiSelect
|
||||
oldValue = field.val()
|
||||
if oldValue
|
||||
value = "#{oldValue},#{value}"
|
||||
else
|
||||
@dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS
|
||||
field.remove()
|
||||
|
||||
# Toggle active class for the tick mark
|
||||
el.toggleClass "is-active"
|
||||
|
||||
if value
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
$.fn.glDropdown = (opts) ->
|
||||
return @.each ->
|
||||
new GitLabDropdown @, opts
|
|
@ -0,0 +1,11 @@
|
|||
class @IssueStatusSelect
|
||||
constructor: ->
|
||||
$('.js-issue-status').each (i, el) ->
|
||||
fieldName = $(el).data("field-name")
|
||||
|
||||
$(el).glDropdown(
|
||||
selectable: true
|
||||
fieldName: fieldName
|
||||
id: (obj, el) ->
|
||||
$(el).data("id")
|
||||
)
|
|
@ -0,0 +1,92 @@
|
|||
class @LabelsSelect
|
||||
constructor: ->
|
||||
$('.js-label-select').each (i, dropdown) ->
|
||||
projectId = $(dropdown).data('project-id')
|
||||
labelUrl = $(dropdown).data("labels")
|
||||
selectedLabel = $(dropdown).data('selected')
|
||||
if selectedLabel
|
||||
selectedLabel = selectedLabel.split(",")
|
||||
newLabelField = $('#new_label_name')
|
||||
newColorField = $('#new_label_color')
|
||||
showNo = $(dropdown).data('show-no')
|
||||
showAny = $(dropdown).data('show-any')
|
||||
|
||||
if newLabelField.length
|
||||
$('.suggest-colors-dropdown a').on "click", (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
newColorField.val $(this).data("color")
|
||||
$('.js-dropdown-label-color-preview')
|
||||
.css 'background-color', $(this).data("color")
|
||||
.addClass 'is-active'
|
||||
|
||||
$('.js-new-label-btn').on "click", (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if newLabelField.val() isnt "" && newColorField.val() isnt ""
|
||||
$('.js-new-label-btn').disable()
|
||||
|
||||
# Create new label with API
|
||||
Api.newLabel projectId, {
|
||||
name: newLabelField.val()
|
||||
color: newColorField.val()
|
||||
}, (label) ->
|
||||
$('.js-new-label-btn').enable()
|
||||
$('.dropdown-menu-back', $(dropdown).parent()).trigger "click"
|
||||
|
||||
$(dropdown).glDropdown(
|
||||
data: (term, callback) ->
|
||||
# We have to fetch the JS version of the labels list because there is no
|
||||
# public facing JSON url for labels
|
||||
$.ajax(
|
||||
url: labelUrl
|
||||
).done (data) ->
|
||||
html = $(data)
|
||||
data = []
|
||||
html.find('.label-row a').each ->
|
||||
data.push(
|
||||
title: $(@).text().trim()
|
||||
)
|
||||
|
||||
if showNo
|
||||
data.unshift(
|
||||
id: "0"
|
||||
title: 'No label'
|
||||
)
|
||||
|
||||
if showAny
|
||||
data.unshift(
|
||||
title: 'Any label'
|
||||
)
|
||||
|
||||
if data.length > 2
|
||||
data.splice 2, 0, "divider"
|
||||
|
||||
callback data
|
||||
renderRow: (label) ->
|
||||
if $.isArray(selectedLabel)
|
||||
selected = ""
|
||||
$.each selectedLabel, (i, selectedLbl) ->
|
||||
selectedLbl = selectedLbl.trim()
|
||||
if selected is "" && label.title is selectedLbl
|
||||
selected = "is-active"
|
||||
else
|
||||
selected = if label.title is selectedLabel then "is-active" else ""
|
||||
|
||||
"<li>
|
||||
<a href='#' class='#{selected}'>
|
||||
#{label.title}
|
||||
</a>
|
||||
</li>"
|
||||
filterable: true
|
||||
search:
|
||||
fields: ['title']
|
||||
selectable: true
|
||||
fieldName: $(dropdown).data('field-name')
|
||||
id: (label) ->
|
||||
label.title
|
||||
clicked: ->
|
||||
if $(dropdown).hasClass "js-filter-submit"
|
||||
$(dropdown).parents('form').submit()
|
||||
)
|
|
@ -0,0 +1,60 @@
|
|||
class @MilestoneSelect
|
||||
constructor: ->
|
||||
$('.js-milestone-select').each (i, dropdown) ->
|
||||
projectId = $(dropdown).data('project-id')
|
||||
milestonesUrl = $(dropdown).data('milestones')
|
||||
selectedMilestone = $(dropdown).data('selected')
|
||||
showNo = $(dropdown).data('show-no')
|
||||
showAny = $(dropdown).data('show-any')
|
||||
useId = $(dropdown).data('use-id')
|
||||
|
||||
$(dropdown).glDropdown(
|
||||
data: (term, callback) ->
|
||||
$.ajax(
|
||||
url: milestonesUrl
|
||||
).done (data) ->
|
||||
html = $(data)
|
||||
data = []
|
||||
html.find('.milestone strong a').each ->
|
||||
link = $(@).attr("href").split("/")
|
||||
data.push(
|
||||
id: link[link.length - 1]
|
||||
title: $(@).text().trim()
|
||||
)
|
||||
|
||||
if showNo
|
||||
data.unshift(
|
||||
id: "0"
|
||||
title: 'No Milestone'
|
||||
)
|
||||
|
||||
if showAny
|
||||
data.unshift(
|
||||
title: 'Any Milestone'
|
||||
)
|
||||
|
||||
if data.length > 2
|
||||
data.splice 2, 0, "divider"
|
||||
|
||||
callback(data)
|
||||
filterable: true
|
||||
search:
|
||||
fields: ['title']
|
||||
selectable: true
|
||||
fieldName: $(dropdown).data('field-name')
|
||||
text: (milestone) ->
|
||||
milestone.title
|
||||
id: (milestone) ->
|
||||
if !useId
|
||||
if milestone.title isnt "Any milestone"
|
||||
milestone.title
|
||||
else
|
||||
""
|
||||
else
|
||||
milestone.id
|
||||
isSelected: (milestone) ->
|
||||
milestone.title is selectedMilestone
|
||||
clicked: ->
|
||||
if $(dropdown).hasClass "js-filter-submit"
|
||||
$(dropdown).parents('form').submit()
|
||||
)
|
|
@ -3,6 +3,81 @@ class @UsersSelect
|
|||
@usersPath = "/autocomplete/users.json"
|
||||
@userPath = "/autocomplete/users/:id.json"
|
||||
|
||||
$('.js-user-search').each (i, dropdown) =>
|
||||
@projectId = $(dropdown).data('project-id')
|
||||
@showCurrentUser = $(dropdown).data('current-user')
|
||||
showNullUser = $(dropdown).data('null-user')
|
||||
showAnyUser = $(dropdown).data('any-user')
|
||||
firstUser = $(dropdown).data('first-user')
|
||||
selectedId = $(dropdown).data('selected')
|
||||
|
||||
$(dropdown).glDropdown(
|
||||
data: (term, callback) =>
|
||||
@users term, (users) =>
|
||||
if term.length is 0
|
||||
showDivider = 0
|
||||
|
||||
if firstUser
|
||||
# Move current user to the front of the list
|
||||
for obj, index in users
|
||||
if obj.username == firstUser
|
||||
users.splice(index, 1)
|
||||
users.unshift(obj)
|
||||
break
|
||||
|
||||
if showNullUser
|
||||
showDivider += 1
|
||||
users.unshift(
|
||||
name: 'Unassigned',
|
||||
id: 0
|
||||
)
|
||||
|
||||
if showAnyUser
|
||||
showDivider += 1
|
||||
name = showAnyUser
|
||||
name = 'Any User' if name == true
|
||||
anyUser = {
|
||||
name: name,
|
||||
id: null
|
||||
}
|
||||
users.unshift(anyUser)
|
||||
|
||||
if showDivider
|
||||
users.splice(showDivider, 0, "divider")
|
||||
|
||||
# Send the data back
|
||||
callback users
|
||||
filterable: true
|
||||
filterRemote: true
|
||||
search:
|
||||
fields: ['name', 'username']
|
||||
selectable: true
|
||||
fieldName: $(dropdown).data('field-name')
|
||||
clicked: ->
|
||||
if $(dropdown).hasClass "js-filter-submit"
|
||||
$(dropdown).parents('form').submit()
|
||||
renderRow: (user) ->
|
||||
username = if user.username then "@#{user.username}" else ""
|
||||
avatar = if user.avatar_url then user.avatar_url else false
|
||||
selected = if user.id is selectedId then "is-active" else ""
|
||||
img = ""
|
||||
|
||||
if avatar
|
||||
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
|
||||
|
||||
"<li>
|
||||
<a href='#' class='dropdown-menu-user-link #{selected}'>
|
||||
#{img}
|
||||
<strong class='dropdown-menu-user-full-name'>
|
||||
#{user.name}
|
||||
</strong>
|
||||
<span class='dropdown-menu-user-username'>
|
||||
#{username}
|
||||
</span>
|
||||
</a>
|
||||
</li>"
|
||||
)
|
||||
|
||||
$('.ajax-users-select').each (i, select) =>
|
||||
@projectId = $(select).data('project-id')
|
||||
@groupId = $(select).data('group-id')
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
|
||||
.dropdown-menu-toggle {
|
||||
position: relative;
|
||||
min-width: 160px;
|
||||
padding: 5px 20px 5px 10px;
|
||||
width: 160px;
|
||||
padding: 6px 20px 6px 10px;
|
||||
background-color: $dropdown-toggle-bg;
|
||||
color: $dropdown-toggle-color;
|
||||
font-size: 15px;
|
||||
|
@ -65,7 +65,7 @@
|
|||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
z-index: 9;
|
||||
width: 240px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 0;
|
||||
|
@ -117,15 +117,19 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.is-focused {
|
||||
background-color: $dropdown-link-hover-bg;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-paging {
|
||||
.dropdown-page-two {
|
||||
.dropdown-page-two,
|
||||
.dropdown-menu-back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -134,7 +138,8 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-page-two {
|
||||
.dropdown-page-two,
|
||||
.dropdown-menu-back {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +162,7 @@
|
|||
.dropdown-menu-user-full-name {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
@ -232,6 +238,7 @@
|
|||
font-size: 14px;
|
||||
border: 0;
|
||||
background: none;
|
||||
outline: 0;
|
||||
|
||||
&:hover {
|
||||
color: darken($dropdown-title-btn-color, 15%);
|
||||
|
@ -298,6 +305,14 @@
|
|||
border-top: 1px solid $dropdown-divider-color;
|
||||
}
|
||||
|
||||
.dropdown-footer-list {
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -317,3 +332,12 @@
|
|||
margin-left: -14px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-labels {
|
||||
.label {
|
||||
position: relative;
|
||||
width: 30px;
|
||||
margin-right: 5px;
|
||||
text-indent: -99999px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.filter-item {
|
||||
margin-right: 6px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
|
|
|
@ -7,6 +7,28 @@
|
|||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.suggest-colors-dropdown {
|
||||
margin-bottom: 5px;
|
||||
|
||||
a {
|
||||
@include border-radius(0);
|
||||
width: 36.7px;
|
||||
margin-right: 0;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-label-color-preview {
|
||||
display: none;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
|
||||
&.is-active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.label-row {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
module DropdownsHelper
|
||||
def dropdown_tag(toggle_text, options: {}, &block)
|
||||
content_tag :div, class: "dropdown" do
|
||||
data_attr = { toggle: "dropdown" }
|
||||
|
||||
if options.has_key?(:data)
|
||||
data_attr = options[:data].merge(data_attr)
|
||||
end
|
||||
|
||||
dropdown_output = dropdown_toggle(toggle_text, data_attr, options)
|
||||
|
||||
dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.has_key?(:dropdown_class)}") do
|
||||
output = ""
|
||||
|
||||
if options.has_key?(:title)
|
||||
output << dropdown_title(options[:title])
|
||||
end
|
||||
|
||||
if options.has_key?(:filter)
|
||||
output << dropdown_filter(options[:placeholder])
|
||||
end
|
||||
|
||||
output << content_tag(:div, class: "dropdown-content") do
|
||||
capture(&block) if block && !options.has_key?(:footer_content)
|
||||
end
|
||||
|
||||
if block && options.has_key?(:footer_content)
|
||||
output << content_tag(:div, class: "dropdown-footer") do
|
||||
capture(&block)
|
||||
end
|
||||
end
|
||||
|
||||
output << dropdown_loading
|
||||
|
||||
output.html_safe
|
||||
end
|
||||
|
||||
dropdown_output.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def dropdown_toggle(toggle_text, data_attr, options)
|
||||
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
|
||||
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text")
|
||||
output << icon('chevron-down')
|
||||
output.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def dropdown_title(title, back: false)
|
||||
content_tag :div, class: "dropdown-title" do
|
||||
title_output = ""
|
||||
|
||||
if back
|
||||
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do
|
||||
icon('arrow-left')
|
||||
end
|
||||
end
|
||||
|
||||
title_output << content_tag(:span, title)
|
||||
|
||||
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
|
||||
icon('times')
|
||||
end
|
||||
|
||||
title_output.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def dropdown_filter(placeholder)
|
||||
content_tag :div, class: "dropdown-input" do
|
||||
filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder
|
||||
filter_output << icon('search')
|
||||
|
||||
filter_output.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def dropdown_content(&block)
|
||||
content_tag(:div, class: "dropdown-content") do
|
||||
if block
|
||||
capture(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dropdown_footer(&block)
|
||||
content_tag(:div, class: "dropdown-footer") do
|
||||
if block
|
||||
capture(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dropdown_loading
|
||||
content_tag :div, class: "dropdown-loading" do
|
||||
icon('spinner spin')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -390,6 +390,51 @@
|
|||
%button.btn.btn-primary
|
||||
Create
|
||||
|
||||
.example
|
||||
%div
|
||||
.dropdown.inline
|
||||
%button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
|
||||
Projects
|
||||
= icon('chevron-down')
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-selectable
|
||||
.dropdown-title
|
||||
%span Go to project
|
||||
%button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
|
||||
= icon('times')
|
||||
.dropdown-input
|
||||
%input.dropdown-input-field{type: "search", placeholder: "Filter results"}
|
||||
= icon('search')
|
||||
.dropdown-content
|
||||
.dropdown-loading
|
||||
= icon('spinner spin')
|
||||
:javascript
|
||||
$('#js-project-dropdown').glDropdown({
|
||||
data: function (term, callback) {
|
||||
Api.projects(term, "last_activity_at", function (data) {
|
||||
callback(data);
|
||||
});
|
||||
},
|
||||
text: function (project) {
|
||||
return project.name_with_namespace || project.name;
|
||||
},
|
||||
selectable: true,
|
||||
fieldName: "author_id",
|
||||
filterable: true,
|
||||
search: {
|
||||
fields: ['name_with_namespace']
|
||||
},
|
||||
id: function (data) {
|
||||
return data.id;
|
||||
},
|
||||
isSelected: function (data) {
|
||||
return data.id === 2;
|
||||
}
|
||||
})
|
||||
|
||||
.example
|
||||
%div
|
||||
= dropdown_tag("Projects", options: { title: "Go to project", filter: true, placeholder: "Filter projects" })
|
||||
|
||||
%h2#panels Panels
|
||||
|
||||
.row
|
||||
|
|
|
@ -7,22 +7,77 @@
|
|||
class: "check_all_issues left"
|
||||
.issues-other-filters
|
||||
.filter-item.inline
|
||||
= users_select_tag(:author_id, selected: params[:author_id],
|
||||
placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
|
||||
- if params[:author_id]
|
||||
= hidden_field_tag(:author_id, params[:author_id])
|
||||
= dropdown_tag("Author", options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
|
||||
placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id" } })
|
||||
|
||||
.filter-item.inline
|
||||
= users_select_tag(:assignee_id, selected: params[:assignee_id],
|
||||
placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
|
||||
- if params[:assignee_id]
|
||||
= hidden_field_tag(:assignee_id, params[:assignee_id])
|
||||
= dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
|
||||
placeholder: "Search assignee", data: { any_user: "Any Author", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } })
|
||||
|
||||
.filter-item.inline.milestone-filter
|
||||
= select_tag('milestone_title', projects_milestones_options,
|
||||
class: 'select2 trigger-submit', include_blank: true,
|
||||
data: {placeholder: 'Milestone'})
|
||||
- if params[:milestone_title]
|
||||
= hidden_field_tag(:milestone_title, params[:milestone_title])
|
||||
= dropdown_tag("Milestone", options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
|
||||
placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do
|
||||
- if @project
|
||||
%ul.dropdown-footer-list
|
||||
- if can? current_user, :admin_milestone, @project
|
||||
%li
|
||||
= link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
|
||||
Create new
|
||||
%li
|
||||
= link_to namespace_project_milestones_path(@project.namespace, @project) do
|
||||
- if can? current_user, :admin_milestone, @project
|
||||
Manage milestones
|
||||
- else
|
||||
View milestones
|
||||
|
||||
.filter-item.inline.labels-filter
|
||||
= select_tag('label_name', projects_labels_options,
|
||||
class: 'select2 trigger-submit', include_blank: true,
|
||||
data: {placeholder: 'Label'})
|
||||
- if params[:label_name]
|
||||
= hidden_field_tag(:label_name, params[:label_name])
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}}
|
||||
%span.dropdown-toggle-text
|
||||
Label
|
||||
= icon('chevron-down')
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
|
||||
.dropdown-page-one
|
||||
= dropdown_title("Filter by label")
|
||||
= dropdown_filter("Search labels")
|
||||
= dropdown_content
|
||||
- if @project
|
||||
= dropdown_footer do
|
||||
%ul.dropdown-footer-list
|
||||
- if can? current_user, :admin_label, @project
|
||||
%li
|
||||
%a.dropdown-toggle-page{href: "#"}
|
||||
Create new
|
||||
%li
|
||||
= link_to namespace_project_labels_path(@project.namespace, @project) do
|
||||
- if can? current_user, :admin_label, @project
|
||||
Manage labels
|
||||
- else
|
||||
View labels
|
||||
- if can? current_user, :admin_label, @project
|
||||
.dropdown-page-two
|
||||
= dropdown_title("Create new label", back: true)
|
||||
= dropdown_content do
|
||||
%input#new_label_color{type: "hidden"}
|
||||
%input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
|
||||
.dropdown-label-color-preview.js-dropdown-label-color-preview
|
||||
.suggest-colors.suggest-colors-dropdown
|
||||
- suggested_colors.each do |color|
|
||||
= link_to '#', style: "background-color: #{color}", data: { color: color } do
|
||||
 
|
||||
%button.btn.btn-primary.js-new-label-btn{type: "button"}
|
||||
Create
|
||||
= dropdown_loading
|
||||
.dropdown-loading
|
||||
= icon('spinner spin')
|
||||
|
||||
.pull-right
|
||||
= render 'shared/sort_dropdown'
|
||||
|
@ -31,11 +86,18 @@
|
|||
.issues_bulk_update.hide
|
||||
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
|
||||
.filter-item.inline
|
||||
= select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" })
|
||||
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
|
||||
%ul
|
||||
%li
|
||||
%a{href: "#", data: {id: "reopen"}} Open
|
||||
%li
|
||||
%a{href: "#", data: {id: "close"}} Closed
|
||||
.filter-item.inline
|
||||
= users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
|
||||
= dropdown_tag("Assignee", options: { toggle_class: "js-user-search", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
|
||||
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
|
||||
.filter-item.inline
|
||||
= select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" })
|
||||
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable",
|
||||
placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :js), use_id: true } })
|
||||
= hidden_field_tag 'update[issues_ids]', []
|
||||
= hidden_field_tag :state_event, params[:state_event]
|
||||
.filter-item.inline
|
||||
|
@ -47,6 +109,9 @@
|
|||
|
||||
:javascript
|
||||
new UsersSelect();
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
new IssueStatusSelect();
|
||||
$('form.filter-form').on('submit', function (event) {
|
||||
event.preventDefault();
|
||||
Turbolinks.visit(this.action + '&' + $(this).serialize());
|
||||
|
|
|
@ -36,13 +36,22 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click "Authored by me" link' do
|
||||
select2(current_user.id, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
execute_script('$("#assignee_id").val("")')
|
||||
execute_script('$(".js-user-search").first().click()')
|
||||
sleep 1
|
||||
execute_script("$('.dropdown-content li:contains(\"#{current_user.to_reference}\") a').click()")
|
||||
sleep 1
|
||||
end
|
||||
|
||||
step 'I click "All" link' do
|
||||
select2(nil, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
execute_script('$(".js-user-search").first().click()')
|
||||
sleep 1
|
||||
execute_script('$(".js-user-search").first().parent().find("li a").first().click()')
|
||||
sleep 1
|
||||
execute_script('$(".js-user-search").eq(1).click()')
|
||||
sleep 1
|
||||
execute_script('$(".js-user-search").eq(1).parent().find("li a").first().click()')
|
||||
sleep 1
|
||||
end
|
||||
|
||||
def should_see(issue)
|
||||
|
|
|
@ -40,13 +40,22 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click "Authored by me" link' do
|
||||
select2(current_user.id, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
execute_script('$("#assignee_id").val("")')
|
||||
execute_script('$(".js-user-search").first().click()')
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-content li:contains(\"#{current_user.to_reference}\") a').click()")
|
||||
sleep 2
|
||||
end
|
||||
|
||||
step 'I click "All" link' do
|
||||
select2(nil, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
execute_script('$(".js-user-search").first().click()')
|
||||
sleep 0.5
|
||||
execute_script('$(".js-user-search").first().parent().find("li a").first().click()')
|
||||
sleep 2
|
||||
execute_script('$(".js-user-search").eq(1).click()')
|
||||
sleep 0.5
|
||||
execute_script('$(".js-user-search").eq(1).parent().find("li a").first().click()')
|
||||
sleep 2
|
||||
end
|
||||
|
||||
def should_see(merge_request)
|
||||
|
|
|
@ -29,7 +29,10 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click link "bug"' do
|
||||
select2('bug', from: "#label_name")
|
||||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
|
||||
sleep 2
|
||||
end
|
||||
|
||||
step 'I click link "feature"' do
|
||||
|
|
|
@ -27,7 +27,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click link "Closed"' do
|
||||
click_link "Closed"
|
||||
find('.issues-state-filters a', text: "Closed").click
|
||||
end
|
||||
|
||||
step 'I click button "Unsubscribe"' do
|
||||
|
@ -63,14 +63,15 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click "author" dropdown' do
|
||||
first('#s2id_author_id').click
|
||||
page.find('.js-author-search').click
|
||||
sleep 1
|
||||
end
|
||||
|
||||
step 'I see current user as the first user' do
|
||||
expect(page).to have_selector('.user-result', visible: true, count: 3)
|
||||
users = page.all('.user-name')
|
||||
expect(page).to have_selector('.dropdown-content', visible: true)
|
||||
users = page.all('.dropdown-menu-author .dropdown-content li a')
|
||||
expect(users[0].text).to eq 'Any Author'
|
||||
expect(users[1].text).to eq current_user.name
|
||||
expect(users[1].text).to eq "#{current_user.name} #{current_user.to_reference}"
|
||||
end
|
||||
|
||||
step 'I submit new issue "500 error on profile"' do
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'Issue filtering by Milestone', feature: true do
|
||||
include Select2Helper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
|
@ -31,6 +29,9 @@ feature 'Issue filtering by Milestone', feature: true do
|
|||
end
|
||||
|
||||
def filter_by_milestone(title)
|
||||
select2(title, from: '#milestone_title')
|
||||
find(".js-milestone-select").click
|
||||
sleep 0.5
|
||||
find(".milestone-filter a", text: title).click
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'Merge Request filtering by Milestone', feature: true do
|
||||
include Select2Helper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
|
@ -31,6 +29,9 @@ feature 'Merge Request filtering by Milestone', feature: true do
|
|||
end
|
||||
|
||||
def filter_by_milestone(title)
|
||||
select2(title, from: '#milestone_title')
|
||||
find(".js-milestone-select").click
|
||||
sleep 0.5
|
||||
find(".milestone-filter a", text: title).click
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue