Added tree selection attribute for object manager.
This commit is contained in:
parent
284dbb3de8
commit
fb8130da18
23 changed files with 819 additions and 91 deletions
|
@ -15,7 +15,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
attribute: attribute
|
attribute: attribute
|
||||||
params: params
|
params: params
|
||||||
))
|
))
|
||||||
@[localParams.data_type](element, localParams, params)
|
@[localParams.data_type](element, localParams, params, attribute)
|
||||||
localItem.find('.js-dataMap').html(element)
|
localItem.find('.js-dataMap').html(element)
|
||||||
localItem.find('.js-dataScreens').html(@dataScreens(attribute, localParams, params))
|
localItem.find('.js-dataScreens').html(@dataScreens(attribute, localParams, params))
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
date: 'Date'
|
date: 'Date'
|
||||||
input: 'Text'
|
input: 'Text'
|
||||||
select: 'Select'
|
select: 'Select'
|
||||||
|
tree_select: 'Tree Select'
|
||||||
boolean: 'Boolean'
|
boolean: 'Boolean'
|
||||||
integer: 'Integer'
|
integer: 'Integer'
|
||||||
|
|
||||||
|
@ -308,6 +309,69 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
lastSelected = value
|
lastSelected = value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@buildRow: (element, child, level = 0, parentElement) ->
|
||||||
|
newRow = element.find('.js-template').clone().removeClass('js-template')
|
||||||
|
newRow.find('.js-key').attr('level', level)
|
||||||
|
newRow.find('.js-key').val(child.name)
|
||||||
|
newRow.find('td').first().css('padding-left', "#{(level * 20) + 10}px")
|
||||||
|
if level is 5
|
||||||
|
newRow.find('.js-addChild').addClass('hide')
|
||||||
|
|
||||||
|
if parentElement
|
||||||
|
parentElement.after(newRow)
|
||||||
|
return
|
||||||
|
|
||||||
|
element.find('.js-treeTable').append(newRow)
|
||||||
|
if child.children
|
||||||
|
for subChild in child.children
|
||||||
|
@buildRow(element, subChild, level + 1)
|
||||||
|
|
||||||
|
@tree_select: (item, localParams, params, attribute) ->
|
||||||
|
params.data_option ||= {}
|
||||||
|
params.data_option.options ||= []
|
||||||
|
if _.isEmpty(params.data_option.options)
|
||||||
|
@buildRow(item, {})
|
||||||
|
else
|
||||||
|
for child in params.data_option.options
|
||||||
|
@buildRow(item, child)
|
||||||
|
|
||||||
|
item.on('click', '.js-addRow', (e) =>
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
addRow = $(e.currentTarget).closest('tr')
|
||||||
|
level = parseInt(addRow.find('.js-key').attr('level'))
|
||||||
|
@buildRow(item, {}, level, addRow)
|
||||||
|
)
|
||||||
|
|
||||||
|
item.on('click', '.js-addChild', (e) =>
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
addRow = $(e.currentTarget).closest('tr')
|
||||||
|
level = parseInt(addRow.find('.js-key').attr('level')) + 1
|
||||||
|
@buildRow(item, {}, level, addRow)
|
||||||
|
)
|
||||||
|
|
||||||
|
item.on('click', '.js-remove', (e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPro
|
||||||
|
element = $(e.target).closest('tr')
|
||||||
|
level = parseInt(element.find('.js-key').attr('level'))
|
||||||
|
subElements = 0
|
||||||
|
nextElement = element
|
||||||
|
elementsToDelete = [element]
|
||||||
|
loop
|
||||||
|
nextElement = nextElement.next()
|
||||||
|
break if !nextElement.get(0)
|
||||||
|
nextLevel = parseInt(nextElement.find('.js-key').attr('level'))
|
||||||
|
break if nextLevel <= level
|
||||||
|
subElements += 1
|
||||||
|
elementsToDelete.push nextElement
|
||||||
|
return if subElements isnt 0 && !confirm("Delete #{subElements} sub elements?")
|
||||||
|
for element in elementsToDelete
|
||||||
|
element.remove()
|
||||||
|
)
|
||||||
|
|
||||||
@boolean: (item, localParams, params) ->
|
@boolean: (item, localParams, params) ->
|
||||||
lastSelected = undefined
|
lastSelected = undefined
|
||||||
item.on('click', '.js-selected', (e) ->
|
item.on('click', '.js-selected', (e) ->
|
||||||
|
|
|
@ -9,24 +9,24 @@ class App.UiElement.searchable_select extends App.UiElement.ApplicationUiElement
|
||||||
attribute.multiple = ''
|
attribute.multiple = ''
|
||||||
|
|
||||||
# build options list based on config
|
# build options list based on config
|
||||||
@getConfigOptionList( attribute, params )
|
@getConfigOptionList(attribute, params)
|
||||||
|
|
||||||
# build options list based on relation
|
# build options list based on relation
|
||||||
@getRelationOptionList( attribute, params )
|
@getRelationOptionList(attribute, params)
|
||||||
|
|
||||||
# add null selection if needed
|
# add null selection if needed
|
||||||
@addNullOption( attribute, params )
|
@addNullOption(attribute, params)
|
||||||
|
|
||||||
# sort attribute.options
|
# sort attribute.options
|
||||||
@sortOptions( attribute, params )
|
@sortOptions(attribute, params)
|
||||||
|
|
||||||
# finde selected/checked item of list
|
# finde selected/checked item of list
|
||||||
@selectedOptions( attribute, params )
|
@selectedOptions(attribute, params)
|
||||||
|
|
||||||
# disable item of list
|
# disable item of list
|
||||||
@disabledOptions( attribute, params )
|
@disabledOptions(attribute, params)
|
||||||
|
|
||||||
# filter attributes
|
# filter attributes
|
||||||
@filterOption( attribute, params )
|
@filterOption(attribute, params)
|
||||||
|
|
||||||
new App.SearchableSelect( attribute: attribute ).element()
|
new App.SearchableSelect(attribute: attribute).element()
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# coffeelint: disable=camel_case_classes
|
||||||
|
class App.UiElement.tree_select extends App.UiElement.ApplicationUiElement
|
||||||
|
@optionsSelect: (children, value) ->
|
||||||
|
return if !children
|
||||||
|
for child in children
|
||||||
|
if child.value is value
|
||||||
|
child.selected = true
|
||||||
|
if child.children
|
||||||
|
@optionsSelect(child.children, value)
|
||||||
|
|
||||||
|
@render: (attribute, params) ->
|
||||||
|
|
||||||
|
# set multiple option
|
||||||
|
if attribute.multiple
|
||||||
|
attribute.multiple = 'multiple'
|
||||||
|
else
|
||||||
|
attribute.multiple = ''
|
||||||
|
|
||||||
|
# build options list based on config
|
||||||
|
@getConfigOptionList(attribute, params)
|
||||||
|
|
||||||
|
# build options list based on relation
|
||||||
|
@getRelationOptionList(attribute, params)
|
||||||
|
|
||||||
|
# add null selection if needed
|
||||||
|
@addNullOption(attribute, params)
|
||||||
|
|
||||||
|
# sort attribute.options
|
||||||
|
@sortOptions(attribute, params)
|
||||||
|
|
||||||
|
# finde selected/checked item of list
|
||||||
|
if attribute.options
|
||||||
|
@optionsSelect(attribute.options, attribute.value)
|
||||||
|
|
||||||
|
# disable item of list
|
||||||
|
@disabledOptions(attribute, params)
|
||||||
|
|
||||||
|
# filter attributes
|
||||||
|
@filterOption(attribute, params)
|
||||||
|
|
||||||
|
new App.SearchableSelect(attribute: attribute).element()
|
|
@ -1,4 +1,46 @@
|
||||||
# coffeelint: disable=duplicate_key
|
# coffeelint: disable=duplicate_key
|
||||||
|
treeParams = (e, params) ->
|
||||||
|
tree = []
|
||||||
|
lastLevel = 0
|
||||||
|
lastLevels = []
|
||||||
|
valueLevels = []
|
||||||
|
|
||||||
|
$(e.target).closest('.modal').find('.js-treeTable .js-key').each( ->
|
||||||
|
$element = $(@)
|
||||||
|
level = parseInt($element.attr('level'))
|
||||||
|
name = $element.val()
|
||||||
|
item =
|
||||||
|
name: name
|
||||||
|
|
||||||
|
if level is 0
|
||||||
|
tree.push item
|
||||||
|
else if lastLevels[level-1]
|
||||||
|
lastLevels[level-1].children ||= []
|
||||||
|
lastLevels[level-1].children.push item
|
||||||
|
else
|
||||||
|
console.log('ERROR', item)
|
||||||
|
if level is 0
|
||||||
|
valueLevels = []
|
||||||
|
else if lastLevel is level
|
||||||
|
valueLevels.pop()
|
||||||
|
else if lastLevel > level
|
||||||
|
down = lastLevel - level
|
||||||
|
for count in [1..down]
|
||||||
|
valueLevels.pop()
|
||||||
|
if lastLevel <= level
|
||||||
|
valueLevels.push name
|
||||||
|
|
||||||
|
item.value = valueLevels.join('::')
|
||||||
|
lastLevels[level] = item
|
||||||
|
lastLevel = level
|
||||||
|
|
||||||
|
)
|
||||||
|
if tree[0]
|
||||||
|
if !params.data_option
|
||||||
|
params.data_option = {}
|
||||||
|
params.data_option.options = tree
|
||||||
|
params
|
||||||
|
|
||||||
class Index extends App.ControllerTabs
|
class Index extends App.ControllerTabs
|
||||||
requiredPermission: 'admin.object'
|
requiredPermission: 'admin.object'
|
||||||
constructor: ->
|
constructor: ->
|
||||||
|
@ -135,6 +177,7 @@ class New extends App.ControllerGenericNew
|
||||||
|
|
||||||
onSubmit: (e) =>
|
onSubmit: (e) =>
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
params = treeParams(e, params)
|
||||||
|
|
||||||
# show attributes for create_middle in two column style
|
# show attributes for create_middle in two column style
|
||||||
if params.screens && params.screens.create_middle
|
if params.screens && params.screens.create_middle
|
||||||
|
@ -184,6 +227,8 @@ class Edit extends App.ControllerGenericEdit
|
||||||
#if attribute.name is 'data_type'
|
#if attribute.name is 'data_type'
|
||||||
# attribute.disabled = true
|
# attribute.disabled = true
|
||||||
|
|
||||||
|
console.log('configure_attributes', configure_attributes)
|
||||||
|
|
||||||
@controller = new App.ControllerForm(
|
@controller = new App.ControllerForm(
|
||||||
model:
|
model:
|
||||||
configure_attributes: configure_attributes
|
configure_attributes: configure_attributes
|
||||||
|
@ -195,6 +240,7 @@ class Edit extends App.ControllerGenericEdit
|
||||||
|
|
||||||
onSubmit: (e) =>
|
onSubmit: (e) =>
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
params = treeParams(e, params)
|
||||||
|
|
||||||
# show attributes for create_middle in two column style
|
# show attributes for create_middle in two column style
|
||||||
if params.screens && params.screens.create_middle
|
if params.screens && params.screens.create_middle
|
||||||
|
|
|
@ -390,14 +390,14 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
properties:
|
properties:
|
||||||
translateX: 0
|
translateX: 0
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
|
|
||||||
# fade out list
|
# fade out list
|
||||||
@recipientList.velocity
|
@recipientList.velocity
|
||||||
properties:
|
properties:
|
||||||
translateX: '-100%'
|
translateX: '-100%'
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
complete: => @recipientList.height(@organizationList.height())
|
complete: => @recipientList.height(@organizationList.height())
|
||||||
|
|
||||||
hideOrganizationMembers: (e) =>
|
hideOrganizationMembers: (e) =>
|
||||||
|
@ -413,7 +413,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
properties:
|
properties:
|
||||||
translateX: 0
|
translateX: 0
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
|
|
||||||
# reset list height
|
# reset list height
|
||||||
@recipientList.height('')
|
@recipientList.height('')
|
||||||
|
@ -423,7 +423,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
properties:
|
properties:
|
||||||
translateX: '100%'
|
translateX: '100%'
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
complete: => @organizationList.addClass('hide')
|
complete: => @organizationList.addClass('hide')
|
||||||
|
|
||||||
newObject: (e) ->
|
newObject: (e) ->
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
class App.SearchableSelect extends Spine.Controller
|
class App.SearchableSelect extends Spine.Controller
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'input .js-input': 'onInput'
|
'input .js-input': 'onInput'
|
||||||
'blur .js-input': 'onBlur'
|
'blur .js-input': 'onBlur'
|
||||||
'focus .js-input': 'onFocus'
|
'focus .js-input': 'onFocus'
|
||||||
'click .js-option': 'selectItem'
|
'click .js-option': 'selectItem'
|
||||||
'mouseenter .js-option': 'highlightItem'
|
'click .js-enter': 'navigateIn'
|
||||||
'shown.bs.dropdown': 'onDropdownShown'
|
'click .js-back': 'navigateOut'
|
||||||
'hidden.bs.dropdown': 'onDropdownHidden'
|
'mouseenter .js-option': 'highlightItem'
|
||||||
|
'mouseenter .js-enter': 'highlightItem'
|
||||||
|
'mouseenter .js-back': 'highlightItem'
|
||||||
|
'shown.bs.dropdown': 'onDropdownShown'
|
||||||
|
'hidden.bs.dropdown': 'onDropdownHidden'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-option': 'option_items'
|
'.js-dropdown': 'dropdown'
|
||||||
|
'.js-option, .js-enter': 'optionItems'
|
||||||
'.js-input': 'input'
|
'.js-input': 'input'
|
||||||
'.js-shadow': 'shadowInput'
|
'.js-shadow': 'shadowInput'
|
||||||
'.js-optionsList': 'optionsList'
|
'.js-optionsList': 'optionsList'
|
||||||
|
'.js-optionsSubmenu': 'optionsSubmenu'
|
||||||
'.js-autocomplete-invisible': 'invisiblePart'
|
'.js-autocomplete-invisible': 'invisiblePart'
|
||||||
'.js-autocomplete-visible': 'visiblePart'
|
'.js-autocomplete-visible': 'visiblePart'
|
||||||
|
|
||||||
|
@ -27,32 +33,95 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
firstSelected = _.find @options.attribute.options, (option) -> option.selected
|
firstSelected = _.find @attribute.options, (option) -> option.selected
|
||||||
|
|
||||||
if firstSelected
|
if firstSelected
|
||||||
@options.attribute.valueName = firstSelected.name
|
@attribute.valueName = firstSelected.name
|
||||||
@options.attribute.value = firstSelected.value
|
@attribute.value = firstSelected.value
|
||||||
else if @options.attribute.unknown && @options.attribute.value
|
else if @attribute.unknown && @attribute.value
|
||||||
@options.attribute.valueName = @options.attribute.value
|
@attribute.valueName = @attribute.value
|
||||||
|
else if @hasSubmenu @attribute.options
|
||||||
|
@attribute.valueName = @getName @attribute.value, @attribute.options
|
||||||
|
|
||||||
@options.attribute.renderedOptions = App.view('generic/searchable_select_options')
|
@html App.view('generic/searchable_select')
|
||||||
options: @options.attribute.options
|
attribute: @attribute
|
||||||
|
options: @renderAllOptions '', @attribute.options, 0
|
||||||
|
submenus: @renderSubmenus @attribute.options
|
||||||
|
|
||||||
@html App.view('generic/searchable_select')( @options.attribute )
|
# initial data
|
||||||
|
@currentMenu = @findMenuContainingValue @attribute.value
|
||||||
|
@level = @getIndex @currentMenu
|
||||||
|
|
||||||
@input.on 'keydown', @navigate
|
renderSubmenus: (options) ->
|
||||||
|
html = ''
|
||||||
|
if options
|
||||||
|
for option in options
|
||||||
|
if option.children
|
||||||
|
html += App.view('generic/searchable_select_submenu')
|
||||||
|
options: @renderOptions(option.children)
|
||||||
|
parentValue: option.value
|
||||||
|
title: option.name
|
||||||
|
|
||||||
|
if @hasSubmenu(option.children)
|
||||||
|
html += @renderSubmenus option.children
|
||||||
|
html
|
||||||
|
|
||||||
|
hasSubmenu: (options) ->
|
||||||
|
return false if !options
|
||||||
|
for option in options
|
||||||
|
return true if option.children
|
||||||
|
return false
|
||||||
|
|
||||||
|
getName: (value, options) ->
|
||||||
|
for option in options
|
||||||
|
if option.value is value
|
||||||
|
return option.name
|
||||||
|
if option.children
|
||||||
|
name = @getName value, option.children
|
||||||
|
return name if name isnt undefined
|
||||||
|
undefined
|
||||||
|
|
||||||
|
renderOptions: (options) ->
|
||||||
|
html = ''
|
||||||
|
for option in options
|
||||||
|
html += App.view('generic/searchable_select_option')
|
||||||
|
option: option
|
||||||
|
class: if option.children then 'js-enter' else 'js-option'
|
||||||
|
html
|
||||||
|
|
||||||
|
renderAllOptions: (parentName, options, level) ->
|
||||||
|
html = ''
|
||||||
|
if options
|
||||||
|
for option in options
|
||||||
|
className = if option.children then 'js-enter' else 'js-option'
|
||||||
|
if level && level > 0
|
||||||
|
className += ' is-hidden is-child'
|
||||||
|
|
||||||
|
html += App.view('generic/searchable_select_option')
|
||||||
|
option: option
|
||||||
|
class: className
|
||||||
|
detail: parentName
|
||||||
|
|
||||||
|
if option.children
|
||||||
|
html += @renderAllOptions "#{parentName} — #{option.name}", option.children, level+1
|
||||||
|
html
|
||||||
|
|
||||||
onDropdownShown: =>
|
onDropdownShown: =>
|
||||||
@input.on 'click', @stopPropagation
|
@input.on 'click', @stopPropagation
|
||||||
@highlightFirst()
|
@highlightFirst()
|
||||||
|
$(document).on 'keydown.searchable_select', @navigate
|
||||||
|
if @level > 0
|
||||||
|
@showSubmenu(@currentMenu)
|
||||||
@isOpen = true
|
@isOpen = true
|
||||||
|
|
||||||
onDropdownHidden: =>
|
onDropdownHidden: =>
|
||||||
@input.off 'click', @stopPropagation
|
@input.off 'click', @stopPropagation
|
||||||
@option_items.removeClass '.is-active'
|
@unhighlightCurrentItem()
|
||||||
|
$(document).off 'keydown.searchable_select'
|
||||||
@isOpen = false
|
@isOpen = false
|
||||||
|
|
||||||
toggle: =>
|
toggle: =>
|
||||||
|
@currentItem = null
|
||||||
@$('[data-toggle="dropdown"]').dropdown('toggle')
|
@$('[data-toggle="dropdown"]').dropdown('toggle')
|
||||||
|
|
||||||
stopPropagation: (event) ->
|
stopPropagation: (event) ->
|
||||||
|
@ -62,8 +131,8 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
switch event.keyCode
|
switch event.keyCode
|
||||||
when 40 then @nudge event, 1 # down
|
when 40 then @nudge event, 1 # down
|
||||||
when 38 then @nudge event, -1 # up
|
when 38 then @nudge event, -1 # up
|
||||||
when 39 then @fillWithAutocompleteSuggestion event # right
|
when 39 then @autocompleteOrNavigateIn event # right
|
||||||
when 37 then @fillWithAutocompleteSuggestion event # left
|
when 37 then @autocompleteOrNavigateOut event # left
|
||||||
when 13 then @onEnter event
|
when 13 then @onEnter event
|
||||||
when 27 then @onEscape()
|
when 27 then @onEscape()
|
||||||
when 9 then @onTab event
|
when 9 then @onTab event
|
||||||
|
@ -71,12 +140,20 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
onEscape: ->
|
onEscape: ->
|
||||||
@toggle() if @isOpen
|
@toggle() if @isOpen
|
||||||
|
|
||||||
|
getCurrentOptions: ->
|
||||||
|
@currentMenu.find('.js-option, .js-enter, .js-back')
|
||||||
|
|
||||||
|
getOptionIndex: (menu, value) ->
|
||||||
|
menu.find('.js-option, .js-enter').filter("[data-value=\"#{value}\"]").index()
|
||||||
|
|
||||||
nudge: (event, direction) ->
|
nudge: (event, direction) ->
|
||||||
return @toggle() if not @isOpen
|
return @toggle() if not @isOpen
|
||||||
|
|
||||||
|
options = @getCurrentOptions()
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
visibleOptions = @option_items.not('.is-hidden')
|
visibleOptions = options.not('.is-hidden')
|
||||||
highlightedItem = @option_items.filter('.is-active')
|
highlightedItem = options.filter('.is-active')
|
||||||
currentPosition = visibleOptions.index(highlightedItem)
|
currentPosition = visibleOptions.index(highlightedItem)
|
||||||
|
|
||||||
currentPosition += direction
|
currentPosition += direction
|
||||||
|
@ -84,10 +161,24 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
return if currentPosition < 0
|
return if currentPosition < 0
|
||||||
return if currentPosition > visibleOptions.size() - 1
|
return if currentPosition > visibleOptions.size() - 1
|
||||||
|
|
||||||
@option_items.removeClass('is-active')
|
@unhighlightCurrentItem()
|
||||||
visibleOptions.eq(currentPosition).addClass('is-active')
|
@currentItem = visibleOptions.eq(currentPosition)
|
||||||
|
@currentItem.addClass('is-active')
|
||||||
@clearAutocomplete()
|
@clearAutocomplete()
|
||||||
|
|
||||||
|
autocompleteOrNavigateIn: (event) ->
|
||||||
|
if @currentItem.hasClass('js-enter')
|
||||||
|
@navigateIn(event)
|
||||||
|
else
|
||||||
|
@fillWithAutocompleteSuggestion(event)
|
||||||
|
|
||||||
|
autocompleteOrNavigateOut: (event) ->
|
||||||
|
# if we're in a depth then navigateOut
|
||||||
|
if @level != 0
|
||||||
|
@navigateOut(event)
|
||||||
|
else
|
||||||
|
@fillWithAutocompleteSuggestion(event)
|
||||||
|
|
||||||
fillWithAutocompleteSuggestion: (event) ->
|
fillWithAutocompleteSuggestion: (event) ->
|
||||||
if !@suggestion
|
if !@suggestion
|
||||||
return
|
return
|
||||||
|
@ -129,11 +220,96 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@shadowInput.val event.currentTarget.getAttribute('data-value')
|
@shadowInput.val event.currentTarget.getAttribute('data-value')
|
||||||
@shadowInput.trigger('change')
|
@shadowInput.trigger('change')
|
||||||
|
|
||||||
|
navigateIn: (event) ->
|
||||||
|
event.stopPropagation()
|
||||||
|
@navigateDepth(1)
|
||||||
|
|
||||||
|
navigateOut: (event) ->
|
||||||
|
event.stopPropagation()
|
||||||
|
@navigateDepth(-1)
|
||||||
|
|
||||||
|
navigateDepth: (dir) ->
|
||||||
|
return if @animating
|
||||||
|
if dir > 0
|
||||||
|
target = @currentItem.attr('data-value')
|
||||||
|
target_menu = @optionsSubmenu.filter("[data-parent-value=\"#{target}\"]")
|
||||||
|
else
|
||||||
|
target_menu = @findMenuContainingValue @currentMenu.attr('data-parent-value')
|
||||||
|
|
||||||
|
@animateToSubmenu(target_menu, dir)
|
||||||
|
|
||||||
|
@level+=dir
|
||||||
|
|
||||||
|
animateToSubmenu: (target_menu, direction) ->
|
||||||
|
@animating = true
|
||||||
|
target_menu.prop('hidden', false)
|
||||||
|
@dropdown.height(Math.max(target_menu.height(), @currentMenu.height()))
|
||||||
|
oldCurrentItem = @currentItem
|
||||||
|
|
||||||
|
@currentMenu.data('current_item_index', @currentItem.index())
|
||||||
|
# default: 1 (first item after the back button)
|
||||||
|
target_item_index = target_menu.data('current_item_index') || 1
|
||||||
|
# if the direction is out then we know the target item -> its the parent item
|
||||||
|
if direction is -1
|
||||||
|
value = @currentMenu.attr('data-parent-value')
|
||||||
|
target_item_index = @getOptionIndex(target_menu, value)
|
||||||
|
|
||||||
|
@currentItem = target_menu.children().eq(target_item_index)
|
||||||
|
@currentItem.addClass('is-active')
|
||||||
|
|
||||||
|
target_menu.velocity
|
||||||
|
properties:
|
||||||
|
translateX: [0, direction*100+'%']
|
||||||
|
options:
|
||||||
|
duration: 240
|
||||||
|
|
||||||
|
@currentMenu.velocity
|
||||||
|
properties:
|
||||||
|
translateX: [direction*-100+'%', 0]
|
||||||
|
options:
|
||||||
|
duration: 240
|
||||||
|
complete: =>
|
||||||
|
oldCurrentItem.removeClass('is-active')
|
||||||
|
$.Velocity.hook(@currentMenu, 'translateX', '')
|
||||||
|
@currentMenu.prop('hidden', true)
|
||||||
|
@dropdown.height(target_menu.height())
|
||||||
|
@currentMenu = target_menu
|
||||||
|
@animating = false
|
||||||
|
|
||||||
|
showSubmenu: (menu) ->
|
||||||
|
@currentMenu.prop('hidden', true)
|
||||||
|
menu.prop('hidden', false)
|
||||||
|
@dropdown.height(menu.height())
|
||||||
|
|
||||||
|
findMenuContainingValue: (value) ->
|
||||||
|
return @optionsList if !value
|
||||||
|
|
||||||
|
# in case of numbers
|
||||||
|
if !value.split && value.toString
|
||||||
|
value = value.toString()
|
||||||
|
path = value.split('::')
|
||||||
|
if path.length == 1
|
||||||
|
return @optionsList
|
||||||
|
else
|
||||||
|
path.pop()
|
||||||
|
return @optionsSubmenu.filter("[data-parent-value=\"#{path.join('::')}\"]")
|
||||||
|
|
||||||
|
getIndex: (menu) ->
|
||||||
|
parentValue = menu.attr('data-parent-value')
|
||||||
|
return 0 if !parentValue
|
||||||
|
return parentValue.split('::').length
|
||||||
|
|
||||||
onTab: (event) ->
|
onTab: (event) ->
|
||||||
return if not @isOpen
|
return if not @isOpen
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
onEnter: (event) ->
|
onEnter: (event) ->
|
||||||
|
if @currentItem
|
||||||
|
if @currentItem.hasClass('js-enter')
|
||||||
|
return @navigateIn(event)
|
||||||
|
else if @currentItem.hasClass('js-back')
|
||||||
|
return @navigateOut(event)
|
||||||
|
|
||||||
@clearAutocomplete()
|
@clearAutocomplete()
|
||||||
|
|
||||||
if not @isOpen
|
if not @isOpen
|
||||||
|
@ -144,13 +320,14 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
selected = @option_items.filter('.is-active')
|
if @currentItem || !@attribute.unknown
|
||||||
if selected.length || !@options.attribute.unknown
|
valueName = @currentItem.text().trim()
|
||||||
valueName = selected.text().trim()
|
value = @currentItem.attr('data-value')
|
||||||
value = selected.attr('data-value')
|
|
||||||
@input.val valueName
|
@input.val valueName
|
||||||
@shadowInput.val value
|
@shadowInput.val value
|
||||||
|
|
||||||
|
@currentItem = null
|
||||||
|
|
||||||
@input.trigger('change')
|
@input.trigger('change')
|
||||||
@shadowInput.trigger('change')
|
@shadowInput.trigger('change')
|
||||||
@toggle()
|
@toggle()
|
||||||
|
@ -169,32 +346,46 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@query = @input.val()
|
@query = @input.val()
|
||||||
@filterByQuery @query
|
@filterByQuery @query
|
||||||
|
|
||||||
if @options.attribute.unknown
|
if @attribute.unknown
|
||||||
@shadowInput.val @query
|
@shadowInput.val @query
|
||||||
|
|
||||||
filterByQuery: (query) ->
|
filterByQuery: (query) ->
|
||||||
query = escapeRegExp(query)
|
query = escapeRegExp(query)
|
||||||
regex = new RegExp(query.split(' ').join('.*'), 'i')
|
regex = new RegExp(query.split(' ').join('.*'), 'i')
|
||||||
|
|
||||||
@option_items
|
@optionsList.addClass 'is-filtered'
|
||||||
|
|
||||||
|
@optionItems
|
||||||
.addClass 'is-hidden'
|
.addClass 'is-hidden'
|
||||||
.filter ->
|
.filter ->
|
||||||
@textContent.match(regex)
|
@textContent.match(regex)
|
||||||
.removeClass 'is-hidden'
|
.removeClass 'is-hidden'
|
||||||
|
|
||||||
if @options.attribute.unknown && @option_items.length == @option_items.filter('.is-hidden').length
|
if !query
|
||||||
@option_items.removeClass 'is-hidden'
|
@optionItems.filter('.is-child').addClass 'is-hidden'
|
||||||
@option_items.removeClass 'is-active'
|
|
||||||
|
# if all are hidden
|
||||||
|
if @attribute.unknown && @optionItems.length == @optionItems.filter('.is-hidden').length
|
||||||
|
@optionItems.not('.is-child').removeClass 'is-hidden'
|
||||||
|
@unhighlightCurrentItem()
|
||||||
|
@optionsList.removeClass 'is-filtered'
|
||||||
else
|
else
|
||||||
@highlightFirst(true)
|
@highlightFirst(true)
|
||||||
|
|
||||||
highlightFirst: (autocomplete) ->
|
highlightFirst: (autocomplete) ->
|
||||||
first = @option_items.removeClass('is-active').not('.is-hidden').first()
|
@unhighlightCurrentItem()
|
||||||
first.addClass 'is-active'
|
@currentItem = @getCurrentOptions().not('.is-hidden').first()
|
||||||
|
@currentItem.addClass 'is-active'
|
||||||
|
|
||||||
if autocomplete
|
if autocomplete
|
||||||
@autocomplete first.attr('data-value'), first.text().trim()
|
@autocomplete @currentItem.attr('data-value'), @currentItem.text().trim()
|
||||||
|
|
||||||
highlightItem: (event) =>
|
highlightItem: (event) =>
|
||||||
@option_items.removeClass('is-active')
|
@unhighlightCurrentItem()
|
||||||
$(event.currentTarget).addClass('is-active')
|
@currentItem = $(event.currentTarget)
|
||||||
|
@currentItem.addClass('is-active')
|
||||||
|
|
||||||
|
unhighlightCurrentItem: ->
|
||||||
|
return if !@currentItem
|
||||||
|
@currentItem.removeClass('is-active')
|
||||||
|
@currentItem = null
|
||||||
|
|
|
@ -70,8 +70,7 @@ class App.SearchableAjaxSelect extends App.SearchableSelect
|
||||||
options.push data
|
options.push data
|
||||||
|
|
||||||
# fill template with gathered options
|
# fill template with gathered options
|
||||||
@optionsList.html App.view('generic/searchable_select_options')
|
@optionsList.html @renderOptions options
|
||||||
options: options
|
|
||||||
|
|
||||||
# refresh elements
|
# refresh elements
|
||||||
@refreshElements()
|
@refreshElements()
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
<div class="dropdown-toggle" data-toggle="dropdown">
|
<div class="dropdown-toggle" data-toggle="dropdown">
|
||||||
<input
|
<input
|
||||||
class="searchableSelect-shadow form-control js-shadow"
|
class="searchableSelect-shadow form-control js-shadow"
|
||||||
id="<%= @id %>"
|
id="<%= @attribute.id %>"
|
||||||
name="<%= @name %>"
|
name="<%= @attribute.name %>"
|
||||||
<%= @required %>
|
<%= @attribute.required %>
|
||||||
<%= @autofocus %>
|
<%= @attribute.autofocus %>
|
||||||
value="<%= @value %>"
|
value="<%= @attribute.value %>"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="searchableSelect-main form-control js-input<%= " #{ @class }" if @class %>"
|
class="searchableSelect-main form-control js-input<%= " #{ @attribute.class }" if @attribute.class %>"
|
||||||
placeholder="<%= @placeholder %>"
|
placeholder="<%= @attribute.placeholder %>"
|
||||||
value="<%= @valueName %>"
|
value="<%= @attribute.valueName %>"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
<%= @required %>
|
<%= @attribute.required %>
|
||||||
>
|
>
|
||||||
<div class="searchableSelect-autocomplete">
|
<div class="searchableSelect-autocomplete">
|
||||||
<span class="searchableSelect-autocomplete-invisible js-autocomplete-invisible"></span>
|
<span class="searchableSelect-autocomplete-invisible js-autocomplete-invisible"></span>
|
||||||
<span class="searchableSelect-autocomplete-visible js-autocomplete-visible"></span>
|
<span class="searchableSelect-autocomplete-visible js-autocomplete-visible"></span>
|
||||||
</div>
|
</div>
|
||||||
<% if !@ajax: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
<% if !@attribute.ajax: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
||||||
<div class="small loading icon"></div>
|
<div class="small loading icon"></div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="dropdown-menu dropdown-menu-left js-optionsList" role="menu">
|
<div class="dropdown-menu dropdown-menu-left dropdown-menu--has-submenu js-dropdown">
|
||||||
<%- @renderedOptions %>
|
<ul class="js-optionsList" role="menu">
|
||||||
</ul>
|
<%- @options %>
|
||||||
|
</ul>
|
||||||
|
<%- @submenus %>
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<li role="presentation" class="<%= @class %>" data-value="<%= @option.value %>" title="<%= @option.name %><% if @detail: %><%= @detail %><% end %>">
|
||||||
|
<span class="searchableSelect-option-text">
|
||||||
|
<%= @option.name %><% if @detail: %><span class="dropdown-detail"><%= @detail %></span><% end %>
|
||||||
|
</span>
|
||||||
|
<% if @option.children: %>
|
||||||
|
<%- @Icon('arrow-right', 'recipientList-arrow') %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
|
@ -1,5 +0,0 @@
|
||||||
<% if @options: %>
|
|
||||||
<% for option in @options: %>
|
|
||||||
<li role="presentation" class="js-option" data-value="<%= option.value %>"><%= option.name %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<ul class="dropdown-submenu js-optionsSubmenu" role="menu" data-parent-value="<%- @parentValue %>" hidden>
|
||||||
|
<% if @title: %>
|
||||||
|
<li class="dropdown-controls js-back">
|
||||||
|
<%- @Icon('arrow-left') %>
|
||||||
|
<div class="dropdown-title">
|
||||||
|
<%- @title %>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<%- @options %>
|
||||||
|
</ul>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<div>
|
||||||
|
<table class="settings-list js-Table" style="width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%- @T('Key') %>
|
||||||
|
<th style="width: 180px"><%- @T('Action') %>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="js-treeTable"></tbody>
|
||||||
|
</table>
|
||||||
|
<table class="hidden">
|
||||||
|
<tbody>
|
||||||
|
<tr class="js-template">
|
||||||
|
<td class="settings-list-control-cell">
|
||||||
|
<input class="form-control form-control--small js-key" type="text" value="" data-level="" required/>
|
||||||
|
<td class="settings-list-row-control">
|
||||||
|
<div class="btn btn--text js-remove" style="margin-left: -10px;">
|
||||||
|
<%- @Icon('trash') %>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn--text btn--create js-addChild" style="margin-left: -10px;">
|
||||||
|
<%- @Icon('plus-small') %> <%- @T('children') %>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn--text btn--create js-addRow" style="margin-left: -10px;">
|
||||||
|
<%- @Icon('plus-small') %> <%- @T('row') %>
|
||||||
|
</div>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
|
@ -6,6 +6,7 @@
|
||||||
<div class="js-datetime">date time settings</div>
|
<div class="js-datetime">date time settings</div>
|
||||||
<div class="js-date">date settings</div>
|
<div class="js-date">date settings</div>
|
||||||
<div class="js-select">select settings</div>
|
<div class="js-select">select settings</div>
|
||||||
|
<div class="js-tree_selection">tree selection settings</div>
|
||||||
<div class="js-checkbox">checkbox settings</div>
|
<div class="js-checkbox">checkbox settings</div>
|
||||||
<div class="js-boolean">boolean settings</div>
|
<div class="js-boolean">boolean settings</div>
|
||||||
<div class="js-richtext">richtext settings</div>
|
<div class="js-richtext">richtext settings</div>
|
||||||
|
|
|
@ -1396,6 +1396,7 @@ fieldset > .form-group {
|
||||||
.merge-target,
|
.merge-target,
|
||||||
.merge-source {
|
.merge-source {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 33%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
@ -6130,6 +6131,48 @@ footer {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu--has-submenu {
|
||||||
|
overflow: hidden;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
background: hsl(234,10%,19%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown.dropdown--actions .dropdown-controls {
|
||||||
|
@extend .u-clickable;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:not(:hover):not(.is-active) {
|
||||||
|
background: hsl(206,7%,28%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: white;
|
||||||
|
margin-right: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-title {
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-detail {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.recipientList,
|
.recipientList,
|
||||||
.recipientList-organizationMembers {
|
.recipientList-organizationMembers {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -7493,14 +7536,30 @@ output {
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dropdown li:hover:not(.is-active) {
|
&-option-text {
|
||||||
background: none;
|
flex: 1 1 0%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& + .icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dropdown li.is-hidden {
|
&.dropdown li {
|
||||||
display: none;
|
|
||||||
|
&:hover:not(.is-active) {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li:not(.is-active):hover + li {
|
li:not(.is-active):hover + li {
|
||||||
|
|
|
@ -108,7 +108,7 @@ class ObjectManagerAttributesController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if params[:data_option] && !params[:data_option].key?(:default)
|
if params[:data_option] && !params[:data_option].key?(:default)
|
||||||
params[:data_option][:default] = if params[:data_type] =~ /^(input|select)$/
|
params[:data_option][:default] = if params[:data_type] =~ /^(input|select|tree_select)$/
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -115,6 +115,57 @@ possible types
|
||||||
note: 'some additional comment', # optional
|
note: 'some additional comment', # optional
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# tree_select
|
||||||
|
|
||||||
|
data_type: 'tree_select',
|
||||||
|
data_option: {
|
||||||
|
default: 'aa',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
'value' => 'aa',
|
||||||
|
'name' => 'aa (comment)',
|
||||||
|
'children' => [
|
||||||
|
{
|
||||||
|
'value' => 'aaa',
|
||||||
|
'name' => 'aaa (comment)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value' => 'aab',
|
||||||
|
'name' => 'aab (comment)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value' => 'aac',
|
||||||
|
'name' => 'aac (comment)',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value' => 'bb',
|
||||||
|
'name' => 'bb (comment)',
|
||||||
|
'children' => [
|
||||||
|
{
|
||||||
|
'value' => 'bba',
|
||||||
|
'name' => 'aaa (comment)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value' => 'bbb',
|
||||||
|
'name' => 'bbb (comment)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value' => 'bbc',
|
||||||
|
'name' => 'bbc (comment)',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxlength: 200,
|
||||||
|
nulloption: true,
|
||||||
|
null: false,
|
||||||
|
multiple: false, # currently only "false" supported
|
||||||
|
translate: true, # optional
|
||||||
|
note: 'some additional comment', # optional
|
||||||
|
},
|
||||||
|
|
||||||
# checkbox
|
# checkbox
|
||||||
|
|
||||||
data_type: 'checkbox',
|
data_type: 'checkbox',
|
||||||
|
@ -550,7 +601,7 @@ to send no browser reload event, pass false
|
||||||
end
|
end
|
||||||
|
|
||||||
data_type = nil
|
data_type = nil
|
||||||
if attribute.data_type =~ /^input|select|richtext|textarea|checkbox$/
|
if attribute.data_type =~ /^input|select|tree_select|richtext|textarea|checkbox$/
|
||||||
data_type = :string
|
data_type = :string
|
||||||
elsif attribute.data_type =~ /^integer|user_autocompletion$/
|
elsif attribute.data_type =~ /^integer|user_autocompletion$/
|
||||||
data_type = :integer
|
data_type = :integer
|
||||||
|
@ -564,7 +615,7 @@ to send no browser reload event, pass false
|
||||||
|
|
||||||
# change field
|
# change field
|
||||||
if model.column_names.include?(attribute.name)
|
if model.column_names.include?(attribute.name)
|
||||||
if attribute.data_type =~ /^input|select|richtext|textarea|checkbox$/
|
if attribute.data_type =~ /^input|select|tree_select|richtext|textarea|checkbox$/
|
||||||
ActiveRecord::Migration.change_column(
|
ActiveRecord::Migration.change_column(
|
||||||
model.table_name,
|
model.table_name,
|
||||||
attribute.name,
|
attribute.name,
|
||||||
|
@ -603,7 +654,7 @@ to send no browser reload event, pass false
|
||||||
end
|
end
|
||||||
|
|
||||||
# create field
|
# create field
|
||||||
if attribute.data_type =~ /^input|select|richtext|textarea|checkbox$/
|
if attribute.data_type =~ /^input|select|tree_select|richtext|textarea|checkbox$/
|
||||||
ActiveRecord::Migration.add_column(
|
ActiveRecord::Migration.add_column(
|
||||||
model.table_name,
|
model.table_name,
|
||||||
attribute.name,
|
attribute.name,
|
||||||
|
@ -704,7 +755,7 @@ to send no browser reload event, pass false
|
||||||
if !data_type
|
if !data_type
|
||||||
raise 'Need data_type param'
|
raise 'Need data_type param'
|
||||||
end
|
end
|
||||||
if data_type !~ /^(input|user_autocompletion|checkbox|select|datetime|date|tag|richtext|textarea|integer|autocompletion_ajax|boolean|user_permission|active)$/
|
if data_type !~ /^(input|user_autocompletion|checkbox|select|tree_select|datetime|date|tag|richtext|textarea|integer|autocompletion_ajax|boolean|user_permission|active)$/
|
||||||
raise "Invalid data_type param '#{data_type}'"
|
raise "Invalid data_type param '#{data_type}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -735,7 +786,7 @@ to send no browser reload event, pass false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
if data_type == 'select' || data_type == 'checkbox'
|
if data_type == 'select' || data_type == 'tree_select' || data_type == 'checkbox'
|
||||||
raise 'Need data_option[:default] param' if !data_option.key?(:default)
|
raise 'Need data_option[:default] param' if !data_option.key?(:default)
|
||||||
raise 'Invalid data_option[:options] or data_option[:relation] param' if data_option[:options].nil? && data_option[:relation].nil?
|
raise 'Invalid data_option[:options] or data_option[:relation] param' if data_option[:options].nil? && data_option[:relation].nil?
|
||||||
if !data_option.key?(:maxlength)
|
if !data_option.key?(:maxlength)
|
||||||
|
|
22
app/views/tests/form_tree_select.html.erb
Normal file
22
app/views/tests/form_tree_select.html.erb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/tests/qunit-1.21.0.css">
|
||||||
|
<script src="/assets/tests/qunit-1.21.0.js"></script>
|
||||||
|
<script src="/assets/tests/form_tree_select.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="qunit" class="u-dontfold"></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form class="form-stacked pull-left">
|
||||||
|
<div id="forms"></div>
|
||||||
|
<button type="submit" class="btn btn-primary submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -5,6 +5,7 @@ Zammad::Application.routes.draw do
|
||||||
match '/tests_model', to: 'tests#model', via: :get
|
match '/tests_model', to: 'tests#model', via: :get
|
||||||
match '/tests_model_ui', to: 'tests#model_ui', via: :get
|
match '/tests_model_ui', to: 'tests#model_ui', via: :get
|
||||||
match '/tests_form', to: 'tests#form', via: :get
|
match '/tests_form', to: 'tests#form', via: :get
|
||||||
|
match '/tests_form_tree_select', to: 'tests#form_tree_select', via: :get
|
||||||
match '/tests_form_find', to: 'tests#form_find', via: :get
|
match '/tests_form_find', to: 'tests#form_find', via: :get
|
||||||
match '/tests_form_trim', to: 'tests#form_trim', via: :get
|
match '/tests_form_trim', to: 'tests#form_trim', via: :get
|
||||||
match '/tests_form_extended', to: 'tests#form_extended', via: :get
|
match '/tests_form_extended', to: 'tests#form_extended', via: :get
|
||||||
|
|
|
@ -563,8 +563,8 @@ class CreateBase < ActiveRecord::Migration
|
||||||
t.string :name, limit: 200, null: false
|
t.string :name, limit: 200, null: false
|
||||||
t.string :display, limit: 200, null: false
|
t.string :display, limit: 200, null: false
|
||||||
t.string :data_type, limit: 100, null: false
|
t.string :data_type, limit: 100, null: false
|
||||||
t.string :data_option, limit: 8000, null: true
|
t.text :data_option, limit: 800.kilobytes + 1, null: true
|
||||||
t.string :data_option_new, limit: 8000, null: true
|
t.text :data_option_new, limit: 800.kilobytes + 1, null: true
|
||||||
t.boolean :editable, null: false, default: true
|
t.boolean :editable, null: false, default: true
|
||||||
t.boolean :active, null: false, default: true
|
t.boolean :active, null: false, default: true
|
||||||
t.string :screens, limit: 2000, null: true
|
t.string :screens, limit: 2000, null: true
|
||||||
|
|
6
db/migrate/20170619000001_tree_select.rb
Normal file
6
db/migrate/20170619000001_tree_select.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class TreeSelect < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
change_column :object_manager_attributes, :data_option, :text, limit: 800.kilobytes + 1, null: true
|
||||||
|
change_column :object_manager_attributes, :data_option_new, :text, limit: 800.kilobytes + 1, null: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -45,13 +45,13 @@ test( "searchable_select check", function() {
|
||||||
autofocus: true
|
autofocus: true
|
||||||
})
|
})
|
||||||
|
|
||||||
var params = App.ControllerForm.params( el )
|
var params = App.ControllerForm.params(el)
|
||||||
var test_params = {
|
var test_params = {
|
||||||
searchable_select1: '',
|
searchable_select1: '',
|
||||||
searchable_select2: 'bbb',
|
searchable_select2: 'bbb',
|
||||||
searchable_select3: '',
|
searchable_select3: '',
|
||||||
}
|
}
|
||||||
deepEqual( params, test_params, 'form param check' )
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
// change selection
|
// change selection
|
||||||
$('[name="searchable_select1"].js-shadow + .js-input').focus().val('').trigger('input')
|
$('[name="searchable_select1"].js-shadow + .js-input').focus().val('').trigger('input')
|
||||||
|
@ -62,13 +62,13 @@ test( "searchable_select check", function() {
|
||||||
var entries = $element.find('li:not(.is-hidden)').length
|
var entries = $element.find('li:not(.is-hidden)').length
|
||||||
equal(entries, 1, 'dropdown count')
|
equal(entries, 1, 'dropdown count')
|
||||||
$element.find('li:not(.is-hidden)').first().click()
|
$element.find('li:not(.is-hidden)').first().click()
|
||||||
params = App.ControllerForm.params( el )
|
params = App.ControllerForm.params(el)
|
||||||
test_params = {
|
test_params = {
|
||||||
searchable_select1: 'ccc',
|
searchable_select1: 'ccc',
|
||||||
searchable_select2: 'bbb',
|
searchable_select2: 'bbb',
|
||||||
searchable_select3: '',
|
searchable_select3: '',
|
||||||
}
|
}
|
||||||
deepEqual( params, test_params, 'form param check' )
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
$('[name="searchable_select2"].js-shadow + .js-input').focus().val('').trigger('input')
|
$('[name="searchable_select2"].js-shadow + .js-input').focus().val('').trigger('input')
|
||||||
var $element = $('[name="searchable_select2"]').closest('.searchableSelect').find('.js-optionsList')
|
var $element = $('[name="searchable_select2"]').closest('.searchableSelect').find('.js-optionsList')
|
||||||
|
@ -79,13 +79,13 @@ test( "searchable_select check", function() {
|
||||||
equal(entries, 1, 'dropdown count')
|
equal(entries, 1, 'dropdown count')
|
||||||
$element.find('li:not(.is-hidden)').first().click()
|
$element.find('li:not(.is-hidden)').first().click()
|
||||||
|
|
||||||
params = App.ControllerForm.params( el )
|
params = App.ControllerForm.params(el)
|
||||||
test_params = {
|
test_params = {
|
||||||
searchable_select1: 'ccc',
|
searchable_select1: 'ccc',
|
||||||
searchable_select2: 'ccc',
|
searchable_select2: 'ccc',
|
||||||
searchable_select3: '',
|
searchable_select3: '',
|
||||||
}
|
}
|
||||||
deepEqual( params, test_params, 'form param check' )
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
$('[name="searchable_select3"].js-shadow + .js-input').focus().val('').trigger('input')
|
$('[name="searchable_select3"].js-shadow + .js-input').focus().val('').trigger('input')
|
||||||
var $element = $('[name="searchable_select3"]').closest('.searchableSelect').find('.js-optionsList')
|
var $element = $('[name="searchable_select3"]').closest('.searchableSelect').find('.js-optionsList')
|
||||||
|
@ -105,12 +105,12 @@ test( "searchable_select check", function() {
|
||||||
e.keyCode = 13
|
e.keyCode = 13
|
||||||
$('[name="searchable_select3"].js-shadow + .js-input').trigger(e)
|
$('[name="searchable_select3"].js-shadow + .js-input').trigger(e)
|
||||||
|
|
||||||
params = App.ControllerForm.params( el )
|
params = App.ControllerForm.params(el)
|
||||||
test_params = {
|
test_params = {
|
||||||
searchable_select1: 'ccc',
|
searchable_select1: 'ccc',
|
||||||
searchable_select2: 'ccc',
|
searchable_select2: 'ccc',
|
||||||
searchable_select3: 'unknown value',
|
searchable_select3: 'unknown value',
|
||||||
}
|
}
|
||||||
deepEqual( params, test_params, 'form param check' )
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
194
public/assets/tests/form_tree_select.js
Normal file
194
public/assets/tests/form_tree_select.js
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
test("form elements check", function() {
|
||||||
|
$('#forms').append('<hr><h1>form elements check</h1><form id="form1"></form>')
|
||||||
|
var el = $('#form1')
|
||||||
|
new App.ControllerForm({
|
||||||
|
el: el,
|
||||||
|
model: {
|
||||||
|
"configure_attributes": [
|
||||||
|
{
|
||||||
|
"name": "tree_select",
|
||||||
|
"display": "tree_select",
|
||||||
|
"tag": "tree_select",
|
||||||
|
"null": true,
|
||||||
|
"translate": true,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "aa",
|
||||||
|
"name": "yes",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"value": "aa::aaa",
|
||||||
|
"name": "yes1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "aa::aab",
|
||||||
|
"name": "yes2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "aa::aac",
|
||||||
|
"name": "yes3",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb",
|
||||||
|
"name": "bb (comment)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"value": "bb::bba",
|
||||||
|
"name": "yes11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb::bbb",
|
||||||
|
"name": "yes22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb::bbc",
|
||||||
|
"name": "yes33",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
autofocus: true
|
||||||
|
});
|
||||||
|
equal(el.find('[name="tree_select"]').val(), '', 'check tree_select value');
|
||||||
|
equal(el.find('[name="tree_select"]').closest('.searchableSelect').find('.js-input').val(), '', 'check tree_select .js-input value');
|
||||||
|
var params = App.ControllerForm.params(el)
|
||||||
|
var test_params = {
|
||||||
|
tree_select: ''
|
||||||
|
}
|
||||||
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
|
$('#forms').append('<hr><h1>form elements check</h1><form id="form2"></form>')
|
||||||
|
var el = $('#form2')
|
||||||
|
new App.ControllerForm({
|
||||||
|
el: el,
|
||||||
|
model: {
|
||||||
|
"configure_attributes": [
|
||||||
|
{
|
||||||
|
"name": "tree_select",
|
||||||
|
"display": "tree_select",
|
||||||
|
"tag": "tree_select",
|
||||||
|
"null": true,
|
||||||
|
"translate": true,
|
||||||
|
"value": "aa",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "aa",
|
||||||
|
"name": "yes",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"value": "aa::aaa",
|
||||||
|
"name": "yes1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "aa::aab",
|
||||||
|
"name": "yes2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "aa::aac",
|
||||||
|
"name": "yes3",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb",
|
||||||
|
"name": "bb (comment)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"value": "bb::bba",
|
||||||
|
"name": "yes11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb::bbb",
|
||||||
|
"name": "yes22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb::bbc",
|
||||||
|
"name": "yes33",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
autofocus: true
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(el.find('[name="tree_select"]').val(), 'aa', 'check tree_select value');
|
||||||
|
equal(el.find('[name="tree_select"]').closest('.searchableSelect').find('.js-input').val(), 'yes', 'check tree_select .js-input value');
|
||||||
|
var params = App.ControllerForm.params(el)
|
||||||
|
var test_params = {
|
||||||
|
tree_select: 'aa'
|
||||||
|
}
|
||||||
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
|
$('#forms').append('<hr><h1>form elements check</h1><form id="form3"></form>')
|
||||||
|
var el = $('#form3')
|
||||||
|
new App.ControllerForm({
|
||||||
|
el: el,
|
||||||
|
model: {
|
||||||
|
"configure_attributes": [
|
||||||
|
{
|
||||||
|
"name": "tree_select",
|
||||||
|
"display": "tree_select",
|
||||||
|
"tag": "tree_select",
|
||||||
|
"null": true,
|
||||||
|
"translate": true,
|
||||||
|
"value": "aa::aab",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "aa",
|
||||||
|
"name": "yes",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"value": "aa::aaa",
|
||||||
|
"name": "yes1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "aa::aab",
|
||||||
|
"name": "yes2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "aa::aac",
|
||||||
|
"name": "yes3",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb",
|
||||||
|
"name": "bb (comment)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"value": "bb::bba",
|
||||||
|
"name": "yes11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb::bbb",
|
||||||
|
"name": "yes22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "bb::bbc",
|
||||||
|
"name": "yes33",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
autofocus: true
|
||||||
|
});
|
||||||
|
equal(el.find('[name="tree_select"]').val(), 'aa::aab', 'check tree_select value');
|
||||||
|
equal(el.find('[name="tree_select"]').closest('.searchableSelect').find('.js-input').val(), 'yes2', 'check tree_select .js-input value');
|
||||||
|
var params = App.ControllerForm.params(el)
|
||||||
|
var test_params = {
|
||||||
|
tree_select: 'aa::aab'
|
||||||
|
}
|
||||||
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
|
||||||
|
});
|
|
@ -87,6 +87,13 @@ class AAbUnitTest < TestCase
|
||||||
value: '0',
|
value: '0',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
location(url: browser_url + '/tests_form_tree_select')
|
||||||
|
sleep 2
|
||||||
|
match(
|
||||||
|
css: '.result .failed',
|
||||||
|
value: '0',
|
||||||
|
)
|
||||||
|
|
||||||
location(url: browser_url + '/tests_form_column_select')
|
location(url: browser_url + '/tests_form_column_select')
|
||||||
sleep 2
|
sleep 2
|
||||||
match(
|
match(
|
||||||
|
|
Loading…
Reference in a new issue