Fixes #2452 - Slow UI with over 150 ticket attributes.

This commit is contained in:
Martin Edenhofer 2019-01-29 08:06:47 +01:00
parent 46615333f9
commit 2cf614d302
6 changed files with 133 additions and 115 deletions

View file

@ -20,6 +20,10 @@ class App.ControllerForm extends App.Controller
if !@attributes if !@attributes
@attributes = [] @attributes = []
@idPrefix = Math.floor( Math.random() * 999999 ).toString()
if @model.className
@idPrefix = "#{@model.className}_#{@idPrefix}"
# set empty class attributes if needed # set empty class attributes if needed
if !@form if !@form
@form = @formGen() @form = @formGen()
@ -28,6 +32,12 @@ class App.ControllerForm extends App.Controller
@form.prepend('<div class="alert alert--danger js-danger js-alert hide" role="alert"></div>') @form.prepend('<div class="alert alert--danger js-danger js-alert hide" role="alert"></div>')
@form.prepend('<div class="alert alert--success js-success hide" role="alert"></div>') @form.prepend('<div class="alert alert--success js-success hide" role="alert"></div>')
if @handlers
params = App.ControllerForm.params(@form)
for attribute in @attributes
for handler in @handlers
handler(params, attribute, @attributes, @idPrefix, @form, @)
# if element is given, prepend form to it # if element is given, prepend form to it
if @el if @el
@el.prepend(@form) @el.prepend(@form)
@ -36,12 +46,6 @@ class App.ControllerForm extends App.Controller
if @elReplace if @elReplace
@elReplace.html(@form) @elReplace.html(@form)
# trigger change to rebuild shown/hidden item and update sub selections
if typeof @form is 'object'
@form.find('input').trigger('change')
@form.find('textarea').trigger('change')
@form.find('select').trigger('change')
# remove alert on input # remove alert on input
@form.on('input', @hideAlert) @form.on('input', @hideAlert)
@ -87,17 +91,16 @@ class App.ControllerForm extends App.Controller
@attributes.push attribute @attributes.push attribute
attribute_count = 0 attributeCount = 0
className = @model.className + '_' + Math.floor( Math.random() * 999999 ).toString()
for attribute in @attributes for attribute in @attributes
attribute_count = attribute_count + 1 attributeCount = attributeCount + 1
if @isDisabled == true if @isDisabled == true
attribute.disabled = true attribute.disabled = true
# add item # add item
item = @formGenItem(attribute, className, fieldset, attribute_count) item = @formGenItem(attribute, @idPrefix, fieldset, attributeCount)
item.appendTo(fieldset) item.appendTo(fieldset)
# if password, add confirm password item # if password, add confirm password item
@ -112,7 +115,7 @@ class App.ControllerForm extends App.Controller
if !attribute.single if !attribute.single
attribute.display = attribute.display + ' (confirm)' attribute.display = attribute.display + ' (confirm)'
attribute.name = attribute.name + '_confirm' attribute.name = attribute.name + '_confirm'
item = @formGenItem(attribute, className, fieldset, attribute_count) item = @formGenItem(attribute, @idPrefix, fieldset, attributeCount)
item.appendTo(fieldset) item.appendTo(fieldset)
if @fullForm if @fullForm
@ -125,7 +128,7 @@ class App.ControllerForm extends App.Controller
for eventSelector, callback of @events for eventSelector, callback of @events
do (eventSelector, callback) -> do (eventSelector, callback) ->
evs = eventSelector.split(' ') evs = eventSelector.split(' ')
fieldset.find( evs[1] ).bind( evs[0], (e) -> callback(e) ) fieldset.find(evs[1]).bind(evs[0], (e) -> callback(e))
# bind tool tips # bind tool tips
fieldset.find('.js-helpMessage').tooltip() fieldset.find('.js-helpMessage').tooltip()
@ -204,20 +207,19 @@ class App.ControllerForm extends App.Controller
class: 'medium' class: 'medium'
} }
### ###
formGenItem: (attribute_config, classname, form, attribute_count) -> formGenItem: (attribute_config, idPrefix, form, attributeCount) ->
attribute = clone(attribute_config, true) attribute = clone(attribute_config, true)
# create item id # create item id
attribute.id = "#{classname}_#{attribute.name}" attribute.id = "#{idPrefix}_#{attribute.name}"
# set label class name # set label class name
attribute.label_class = @model.labelClass attribute.label_class = @model.labelClass
# set autofocus # set autofocus
if @autofocus && attribute_count is 1 if @autofocus && attributeCount is 1
attribute.autofocus = 'autofocus' attribute.autofocus = 'autofocus'
# set required option # set required option
@ -281,13 +283,13 @@ class App.ControllerForm extends App.Controller
for item in attribute.options for item in attribute.options
if item.value && item.value isnt '' if item.value && item.value isnt ''
attributesNew.value = item.value attributesNew.value = item.value
item = $( App.view('generic/input')( attribute: attributesNew ) ) item = $( App.view('generic/input')(attribute: attributesNew) )
if @handlers if @handlers
item.bind('change', (e) => item.bind('change', (e) =>
params = App.ControllerForm.params( $(e.target) ) params = App.ControllerForm.params($(e.target))
for handler in @handlers for handler in @handlers
handler(params, attribute, @attributes, classname, form, @) handler(params, attribute, @attributes, idPrefix, form, @)
) )
# bind dependency # bind dependency
@ -376,7 +378,7 @@ class App.ControllerForm extends App.Controller
el.find('[name="' + key + '"]').attr('required', false) el.find('[name="' + key + '"]').attr('required', false)
el.find('[name="' + key + '"]').parents('.form-group').find('label span').html('') el.find('[name="' + key + '"]').parents('.form-group').find('label span').html('')
showHideToggle: (params, changedAttribute, attributes, classname, form, ui) -> showHideToggle: (params, changedAttribute, attributes, _classname, form, ui) ->
for attribute in attributes for attribute in attributes
if attribute.shown_if if attribute.shown_if
hit = false hit = false
@ -389,26 +391,26 @@ class App.ControllerForm extends App.Controller
else if params[refAttribute].toString() is refValue.toString() else if params[refAttribute].toString() is refValue.toString()
hit = true hit = true
if hit if hit
ui.show(attribute.name) ui.show(attribute.name, form)
else else
ui.hide(attribute.name) ui.hide(attribute.name, form)
requiredMandantoryToggle: (params, changedAttribute, attributes, classname, form, ui) -> requiredMandantoryToggle: (params, changedAttribute, attributes, _classname, form, ui) ->
for attribute in attributes for attribute in attributes
if attribute.required_if if attribute.required_if
hit = false hit = false
for refAttribute, refValue of attribute.required_if for refAttribute, refValue of attribute.required_if
if params[refAttribute] if params[refAttribute]
if _.isArray( refValue ) if _.isArray(refValue)
for item in refValue for item in refValue
if params[refAttribute].toString() is item.toString() if params[refAttribute].toString() is item.toString()
hit = true hit = true
else if params[refAttribute].toString() is refValue.toString() else if params[refAttribute].toString() is refValue.toString()
hit = true hit = true
if hit if hit
ui.mandantory(attribute.name) ui.mandantory(attribute.name, form)
else else
ui.optional(attribute.name) ui.optional(attribute.name, form)
validate: (params) -> validate: (params) ->
App.Model.validate( App.Model.validate(
@ -512,7 +514,7 @@ class App.ControllerForm extends App.Controller
param[newKey] = null param[newKey] = null
else if param[key] else if param[key]
try try
time = new Date( Date.parse( "#{param[key]}T00:00:00Z" ) ) time = new Date( Date.parse("#{param[key]}T00:00:00Z") )
format = (number) -> format = (number) ->
if parseInt(number) < 10 if parseInt(number) < 10
number = "0#{number}" number = "0#{number}"
@ -534,7 +536,7 @@ class App.ControllerForm extends App.Controller
param[newKey] = null param[newKey] = null
else if param[key] else if param[key]
try try
time = new Date( Date.parse( param[key] ) ) time = new Date( Date.parse(param[key]) )
if time is 'Invalid Datetime' if time is 'Invalid Datetime'
throw "Invalid Datetime #{param[key]}" throw "Invalid Datetime #{param[key]}"
param[newKey] = time.toISOString().replace(/:\d\d\.\d\d\dZ$/, ':00.000Z') param[newKey] = time.toISOString().replace(/:\d\d\.\d\d\dZ$/, ':00.000Z')

View file

@ -14,12 +14,13 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
item = $(App.view('object_manager/attribute')(attribute: attribute)) item = $(App.view('object_manager/attribute')(attribute: attribute))
updateDataMap = (localParams, localAttribute, localAttributes, localClassname, localForm, localA) => updateDataMap = (localParams, localAttribute, localAttributes, localClassname, localForm, localA) =>
localItem = localForm.closest('.js-data') return if !localParams.data_type
element = $(App.view("object_manager/attribute/#{localParams.data_type}")( element = $(App.view("object_manager/attribute/#{localParams.data_type}")(
attribute: attribute attribute: attribute
params: params params: params
)) ))
@[localParams.data_type](element, localParams, params, attribute) @[localParams.data_type](element, localParams, params, attribute)
localItem = localForm.closest('.js-data')
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))
@ -42,6 +43,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
{ name: attribute.name, display: '', tag: 'select', null: false, options: options, translate: true, default: 'input', disabled: attribute.disabled }, { name: attribute.name, display: '', tag: 'select', null: false, options: options, translate: true, default: 'input', disabled: attribute.disabled },
] ]
dataType = new App.ControllerForm( dataType = new App.ControllerForm(
el: item.find('.js-dataType')
model: model:
configure_attributes: configureAttributes configure_attributes: configureAttributes
noFieldset: true noFieldset: true
@ -50,8 +52,8 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
] ]
params: params params: params
) )
item.find('.js-dataType').html(dataType.form)
item.find('.js-boolean').data('field-type', 'boolean') item.find('.js-boolean').data('field-type', 'boolean')
item.find('.js-dataType [name="data_type"]').trigger('change')
item item
@dataScreens: (attribute, localParams, params) -> @dataScreens: (attribute, localParams, params) ->

View file

@ -26,30 +26,32 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement
) ) ) )
# show/hide trees # show/hide trees
item.find('[name=permission_ids]').bind('change', (e) -> item.find('[name=permission_ids]').bind('change', (e) =>
element = $(e.currentTarget) @checkUncheck($(e.currentTarget), permissions, item)
checked = element.prop('checked')
permission_id = element.prop('value')
return if !permission_id
permission = App.Permission.find(permission_id)
return if !permission
if !permission.name.match(/\./)
# show/hide sub permissions
for localPermission in permissions
regexp = new RegExp("^#{permission.name}")
if localPermission.name.match(regexp)
localElement = item.find("[name=permission_ids][value=#{localPermission.id}]").closest('.js-subPermissionList')
if checked
localElement.addClass('hide')
else
localElement.removeClass('hide')
if checked && permission.preferences.not
for localPermission in permission.preferences.not
lookupPermission = App.Permission.findByAttribute('name', localPermission)
if lookupPermission
item.find("[name=permission_ids][value=#{lookupPermission.id}]").prop('checked', false)
) )
item.find('[name=permission_ids]').trigger('change')
item item
@checkUncheck: (element, permissions, item) ->
checked = element.prop('checked')
permission_id = element.prop('value')
return if !permission_id
permission = App.Permission.find(permission_id)
return if !permission
if !permission.name.match(/\./)
# show/hide sub permissions
for localPermission in permissions
regexp = new RegExp("^#{permission.name}")
if localPermission.name.match(regexp)
localElement = item.find("[name=permission_ids][value=#{localPermission.id}]").closest('.js-subPermissionList')
if checked
localElement.addClass('hide')
else
localElement.removeClass('hide')
if checked && permission.preferences.not
for localPermission in permission.preferences.not
lookupPermission = App.Permission.findByAttribute('name', localPermission)
if lookupPermission
item.find("[name=permission_ids][value=#{lookupPermission.id}]").prop('checked', false)

View file

@ -142,7 +142,7 @@ class App.UiElement.postmaster_match
selector = @buildAttributeSelector(groups, attribute) selector = @buildAttributeSelector(groups, attribute)
# scaffold of match elements # scaffold of match elements
item = $( App.view('generic/postmaster_match')( attribute: attribute ) ) item = $( App.view('generic/postmaster_match')(attribute: attribute) )
item.find('.js-attributeSelector').prepend(selector) item.find('.js-attributeSelector').prepend(selector)
# add filter # add filter
@ -163,7 +163,6 @@ class App.UiElement.postmaster_match
item.find('.js-attributeSelector select').bind('change', (e) => item.find('.js-attributeSelector select').bind('change', (e) =>
key = $(e.target).find('option:selected').attr('value') key = $(e.target).find('option:selected').attr('value')
elementRow = $(e.target).closest('.js-filterElement') elementRow = $(e.target).closest('.js-filterElement')
@rebuildAttributeSelectors(item, elementRow, key, attribute) @rebuildAttributeSelectors(item, elementRow, key, attribute)
@rebuildOperater(item, elementRow, key, groups, undefined, attribute) @rebuildOperater(item, elementRow, key, groups, undefined, attribute)
@buildValue(item, elementRow, key, groups, undefined, undefined, attribute) @buildValue(item, elementRow, key, groups, undefined, undefined, attribute)
@ -178,8 +177,9 @@ class App.UiElement.postmaster_match
) )
# build inital params # build inital params
if !_.isEmpty(params[attribute.name]) if _.isEmpty(params[attribute.name])
item.find('.js-filterElement .js-attributeSelector select').trigger('change')
else
selectorExists = false selectorExists = false
for key, meta of params[attribute.name] for key, meta of params[attribute.name]
selectorExists = true selectorExists = true
@ -197,6 +197,8 @@ class App.UiElement.postmaster_match
@buildValue(item, elementClone, key, groups, value, operator, attribute) @buildValue(item, elementClone, key, groups, value, operator, attribute)
elementLast.after(elementClone) elementLast.after(elementClone)
item.find('.js-attributeSelector select').trigger('change')
# remove first dummy row # remove first dummy row
if selectorExists if selectorExists
item.find('.js-filterElement').first().remove() item.find('.js-filterElement').first().remove()

View file

@ -115,7 +115,7 @@ class App.UiElement.postmaster_set
[elements, groups] [elements, groups]
@placeholder: (elementFull, attribute, params = {}, groups) -> @placeholder: (elementFull, attribute, params = {}, groups) ->
item = $( App.view('generic/postmaster_set_row')( attribute: attribute ) ) item = $( App.view('generic/postmaster_set_row')(attribute: attribute) )
selector = @buildAttributeSelector(elementFull, groups, attribute, item) selector = @buildAttributeSelector(elementFull, groups, attribute, item)
item.find('.js-attributeSelector').prepend(selector) item.find('.js-attributeSelector').prepend(selector)
item item
@ -125,7 +125,7 @@ class App.UiElement.postmaster_set
[elements, groups] = @defaults() [elements, groups] = @defaults()
# scaffold of match elements # scaffold of match elements
item = $( App.view('generic/postmaster_set')( attribute: attribute ) ) item = $( App.view('generic/postmaster_set')(attribute: attribute) )
# add filter # add filter
item.on('click', '.js-add', (e) => item.on('click', '.js-add', (e) =>
@ -147,29 +147,37 @@ class App.UiElement.postmaster_set
# change attribute selector # change attribute selector
item.on('change', '.js-attributeSelector select', (e) => item.on('change', '.js-attributeSelector select', (e) =>
key = $(e.target).find('option:selected').attr('value')
elementRow = $(e.target).closest('.js-filterElement') elementRow = $(e.target).closest('.js-filterElement')
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value') groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, elementRow, key, attribute) @rebuildAttributeSelectors(item, elementRow, groupAndAttribute, attribute)
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute) @buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
@buildValue(item, elementRow, key, groups, undefined, undefined, attribute) @buildValue(item, elementRow, groupAndAttribute, groups, undefined, undefined, attribute)
) )
# build inital params # build inital params
if _.isEmpty(params[attribute.name]) if _.isEmpty(params[attribute.name])
item.append(@placeholder(item, attribute, params, groups)) element = @placeholder(item, attribute, params, groups)
item.append(element)
groupAndAttribute = element.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, element, groupAndAttribute, attribute)
@buildOperator(item, element, groupAndAttribute, elements, {}, attribute)
@buildValue(item, element, groupAndAttribute, groups, undefined, undefined, attribute)
return item return item
for key, meta of params[attribute.name] else
operator = meta.operator for key, meta of params[attribute.name]
value = meta.value operator = meta.operator
value = meta.value
# build and append # build and append
element = @placeholder(item, attribute, params, groups) element = @placeholder(item, attribute, params, groups)
@rebuildAttributeSelectors(item, element, key, attribute) groupAndAttribute = element.find('.js-attributeSelector option:selected').attr('value')
@buildValue(item, element, key, groups, value, operator, attribute) @rebuildAttributeSelectors(item, element, key, attribute)
@buildOperator(item, element, key, elements, {}, attribute)
@buildValue(item, element, key, groups, value, operator, attribute)
item.append(element) item.append(element)
item.find('.js-attributeSelector select').trigger('change')
item item

View file

@ -96,53 +96,55 @@ class App.UiElement.user_permission
item.on('click', '.checkbox-replacement', throttled) item.on('click', '.checkbox-replacement', throttled)
# if customer, remove admin and agent # if customer, remove admin and agent
item.find('[name=role_ids]').bind('change', (e) -> item.find('[name=role_ids]').bind('change', (e) =>
element = $(e.currentTarget) @checkUncheck($(e.currentTarget), rolesWithGroupPlugin, item, hideGroups)
checked = element.prop('checked') )
role_id = element.prop('value') item.find('[name=role_ids]').trigger('change')
return if !role_id item
role = App.Role.find(role_id)
return if !role
triggers = []
# deselect conflicting roles @checkUncheck: (element, rolesWithGroupPlugin, item, hideGroups) ->
if checked checked = element.prop('checked')
if role && role.preferences && role.preferences.not role_id = element.prop('value')
for notRole in role.preferences.not return if !role_id
localRole = App.Role.findByAttribute('name', notRole) role = App.Role.find(role_id)
if localRole return if !role
localElement = item.find("[name=role_ids][value=#{localRole.id}]") triggers = []
if localElement.prop('checked')
if !confirm(App.i18n.translateInline('Role %s is conflicting with role %s, do you want to continue?', role.name, localRole.name, localRole.name))
item.find("[name=role_ids][value=#{role_id}]").prop('checked', false)
return
item.find("[name=role_ids][value=#{localRole.id}]").prop('checked', false)
triggers.push item.find("[name=role_ids][value=#{localRole.id}]")
# if role with groups plugin is deselected, hide group selection # deselect conflicting roles
if !checked if checked
show = false if role && role.preferences && role.preferences.not
for role_id, group of rolesWithGroupPlugin for notRole in role.preferences.not
if item.find("[name=role_ids][value=#{role_id}]").prop('checked') localRole = App.Role.findByAttribute('name', notRole)
show = true if localRole
if !show localElement = item.find("[name=role_ids][value=#{localRole.id}]")
item.find('.js-groupList').addClass('hidden') if localElement.prop('checked')
if !confirm(App.i18n.translateInline('Role %s is conflicting with role %s, do you want to continue?', role.name, localRole.name, localRole.name))
item.find("[name=role_ids][value=#{role_id}]").prop('checked', false)
return
item.find("[name=role_ids][value=#{localRole.id}]").prop('checked', false)
triggers.push item.find("[name=role_ids][value=#{localRole.id}]")
# select groups if only one is available # if role with groups plugin is deselected, hide group selection
if hideGroups if !checked
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', false) show = false
return for role_id, group of rolesWithGroupPlugin
if item.find("[name=role_ids][value=#{role_id}]").prop('checked')
# if role with groups plugin is selected, show group selection show = true
if rolesWithGroupPlugin[role_id] is 'group' if !show
item.find('.js-groupList:not(.js-groupListHide)').removeClass('hidden') item.find('.js-groupList').addClass('hidden')
# select groups if only one is available # select groups if only one is available
if hideGroups if hideGroups
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', true) item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', false)
return
for trigger in triggers # if role with groups plugin is selected, show group selection
trigger.trigger('change') if rolesWithGroupPlugin[role_id] is 'group'
) item.find('.js-groupList:not(.js-groupListHide)').removeClass('hidden')
item # select groups if only one is available
if hideGroups
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', true)
for trigger in triggers
trigger.trigger('change')