From 2cf614d302db948374de0b0af596496e056adbb9 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 29 Jan 2019 08:06:47 +0100 Subject: [PATCH] Fixes #2452 - Slow UI with over 150 ticket attributes. --- .../_application_controller_form.coffee | 58 +++++++------ .../object_manager_attribute.coffee | 6 +- .../controllers/_ui_element/permission.coffee | 52 +++++------ .../_ui_element/postmaster_match.coffee | 10 ++- .../_ui_element/postmaster_set.coffee | 36 +++++--- .../_ui_element/user_permission.coffee | 86 ++++++++++--------- 6 files changed, 133 insertions(+), 115 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.coffee index 81651486f..380c36b23 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.coffee @@ -20,6 +20,10 @@ class App.ControllerForm extends App.Controller if !@attributes @attributes = [] + @idPrefix = Math.floor( Math.random() * 999999 ).toString() + if @model.className + @idPrefix = "#{@model.className}_#{@idPrefix}" + # set empty class attributes if needed if !@form @form = @formGen() @@ -28,6 +32,12 @@ class App.ControllerForm extends App.Controller @form.prepend('') @form.prepend('') + 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 @el @el.prepend(@form) @@ -36,12 +46,6 @@ class App.ControllerForm extends App.Controller if @elReplace @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 @form.on('input', @hideAlert) @@ -87,17 +91,16 @@ class App.ControllerForm extends App.Controller @attributes.push attribute - attribute_count = 0 - className = @model.className + '_' + Math.floor( Math.random() * 999999 ).toString() + attributeCount = 0 for attribute in @attributes - attribute_count = attribute_count + 1 + attributeCount = attributeCount + 1 if @isDisabled == true attribute.disabled = true # add item - item = @formGenItem(attribute, className, fieldset, attribute_count) + item = @formGenItem(attribute, @idPrefix, fieldset, attributeCount) item.appendTo(fieldset) # if password, add confirm password item @@ -112,7 +115,7 @@ class App.ControllerForm extends App.Controller if !attribute.single attribute.display = attribute.display + ' (confirm)' attribute.name = attribute.name + '_confirm' - item = @formGenItem(attribute, className, fieldset, attribute_count) + item = @formGenItem(attribute, @idPrefix, fieldset, attributeCount) item.appendTo(fieldset) if @fullForm @@ -125,7 +128,7 @@ class App.ControllerForm extends App.Controller for eventSelector, callback of @events do (eventSelector, callback) -> 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 fieldset.find('.js-helpMessage').tooltip() @@ -204,20 +207,19 @@ class App.ControllerForm extends App.Controller class: 'medium' } - ### - formGenItem: (attribute_config, classname, form, attribute_count) -> + formGenItem: (attribute_config, idPrefix, form, attributeCount) -> attribute = clone(attribute_config, true) # create item id - attribute.id = "#{classname}_#{attribute.name}" + attribute.id = "#{idPrefix}_#{attribute.name}" # set label class name attribute.label_class = @model.labelClass # set autofocus - if @autofocus && attribute_count is 1 + if @autofocus && attributeCount is 1 attribute.autofocus = 'autofocus' # set required option @@ -281,13 +283,13 @@ class App.ControllerForm extends App.Controller for item in attribute.options if item.value && item.value isnt '' attributesNew.value = item.value - item = $( App.view('generic/input')( attribute: attributesNew ) ) + item = $( App.view('generic/input')(attribute: attributesNew) ) if @handlers item.bind('change', (e) => - params = App.ControllerForm.params( $(e.target) ) + params = App.ControllerForm.params($(e.target)) for handler in @handlers - handler(params, attribute, @attributes, classname, form, @) + handler(params, attribute, @attributes, idPrefix, form, @) ) # bind dependency @@ -376,7 +378,7 @@ class App.ControllerForm extends App.Controller el.find('[name="' + key + '"]').attr('required', false) 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 if attribute.shown_if hit = false @@ -389,26 +391,26 @@ class App.ControllerForm extends App.Controller else if params[refAttribute].toString() is refValue.toString() hit = true if hit - ui.show(attribute.name) + ui.show(attribute.name, form) 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 if attribute.required_if hit = false for refAttribute, refValue of attribute.required_if if params[refAttribute] - if _.isArray( refValue ) + if _.isArray(refValue) for item in refValue if params[refAttribute].toString() is item.toString() hit = true else if params[refAttribute].toString() is refValue.toString() hit = true if hit - ui.mandantory(attribute.name) + ui.mandantory(attribute.name, form) else - ui.optional(attribute.name) + ui.optional(attribute.name, form) validate: (params) -> App.Model.validate( @@ -512,7 +514,7 @@ class App.ControllerForm extends App.Controller param[newKey] = null else if param[key] try - time = new Date( Date.parse( "#{param[key]}T00:00:00Z" ) ) + time = new Date( Date.parse("#{param[key]}T00:00:00Z") ) format = (number) -> if parseInt(number) < 10 number = "0#{number}" @@ -534,7 +536,7 @@ class App.ControllerForm extends App.Controller param[newKey] = null else if param[key] try - time = new Date( Date.parse( param[key] ) ) + time = new Date( Date.parse(param[key]) ) if time is 'Invalid Datetime' throw "Invalid Datetime #{param[key]}" param[newKey] = time.toISOString().replace(/:\d\d\.\d\d\dZ$/, ':00.000Z') diff --git a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee index 99ae033f6..320b025bd 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee @@ -14,12 +14,13 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi item = $(App.view('object_manager/attribute')(attribute: attribute)) 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}")( attribute: attribute params: params )) @[localParams.data_type](element, localParams, params, attribute) + localItem = localForm.closest('.js-data') localItem.find('.js-dataMap').html(element) 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 }, ] dataType = new App.ControllerForm( + el: item.find('.js-dataType') model: configure_attributes: configureAttributes noFieldset: true @@ -50,8 +52,8 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi ] params: params ) - item.find('.js-dataType').html(dataType.form) item.find('.js-boolean').data('field-type', 'boolean') + item.find('.js-dataType [name="data_type"]').trigger('change') item @dataScreens: (attribute, localParams, params) -> diff --git a/app/assets/javascripts/app/controllers/_ui_element/permission.coffee b/app/assets/javascripts/app/controllers/_ui_element/permission.coffee index ec1430a4d..a268a9f65 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/permission.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/permission.coffee @@ -26,30 +26,32 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement ) ) # show/hide trees - item.find('[name=permission_ids]').bind('change', (e) -> - element = $(e.currentTarget) - 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]').bind('change', (e) => + @checkUncheck($(e.currentTarget), permissions, item) ) - + item.find('[name=permission_ids]').trigger('change') 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) + diff --git a/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.coffee b/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.coffee index 4ec591b6f..47a742142 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.coffee @@ -142,7 +142,7 @@ class App.UiElement.postmaster_match selector = @buildAttributeSelector(groups, attribute) # 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) # add filter @@ -163,7 +163,6 @@ class App.UiElement.postmaster_match item.find('.js-attributeSelector select').bind('change', (e) => key = $(e.target).find('option:selected').attr('value') elementRow = $(e.target).closest('.js-filterElement') - @rebuildAttributeSelectors(item, elementRow, key, attribute) @rebuildOperater(item, elementRow, key, groups, undefined, attribute) @buildValue(item, elementRow, key, groups, undefined, undefined, attribute) @@ -178,8 +177,9 @@ class App.UiElement.postmaster_match ) # 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 for key, meta of params[attribute.name] selectorExists = true @@ -197,6 +197,8 @@ class App.UiElement.postmaster_match @buildValue(item, elementClone, key, groups, value, operator, attribute) elementLast.after(elementClone) + item.find('.js-attributeSelector select').trigger('change') + # remove first dummy row if selectorExists item.find('.js-filterElement').first().remove() diff --git a/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee b/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee index b3803ed49..bcf475b37 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee @@ -115,7 +115,7 @@ class App.UiElement.postmaster_set [elements, 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) item.find('.js-attributeSelector').prepend(selector) item @@ -125,7 +125,7 @@ class App.UiElement.postmaster_set [elements, groups] = @defaults() # scaffold of match elements - item = $( App.view('generic/postmaster_set')( attribute: attribute ) ) + item = $( App.view('generic/postmaster_set')(attribute: attribute) ) # add filter item.on('click', '.js-add', (e) => @@ -147,29 +147,37 @@ class App.UiElement.postmaster_set # change attribute selector item.on('change', '.js-attributeSelector select', (e) => - key = $(e.target).find('option:selected').attr('value') elementRow = $(e.target).closest('.js-filterElement') 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) - @buildValue(item, elementRow, key, groups, undefined, undefined, attribute) + @buildValue(item, elementRow, groupAndAttribute, groups, undefined, undefined, attribute) ) # build inital params 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 - for key, meta of params[attribute.name] - operator = meta.operator - value = meta.value + else + for key, meta of params[attribute.name] + operator = meta.operator + value = meta.value - # build and append - element = @placeholder(item, attribute, params, groups) - @rebuildAttributeSelectors(item, element, key, attribute) - @buildValue(item, element, key, groups, value, operator, attribute) + # build and append + element = @placeholder(item, attribute, params, groups) + groupAndAttribute = element.find('.js-attributeSelector option:selected').attr('value') + @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 diff --git a/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee b/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee index 988024155..eecfe4e67 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee @@ -96,53 +96,55 @@ class App.UiElement.user_permission item.on('click', '.checkbox-replacement', throttled) # if customer, remove admin and agent - item.find('[name=role_ids]').bind('change', (e) -> - element = $(e.currentTarget) - checked = element.prop('checked') - role_id = element.prop('value') - return if !role_id - role = App.Role.find(role_id) - return if !role - triggers = [] + item.find('[name=role_ids]').bind('change', (e) => + @checkUncheck($(e.currentTarget), rolesWithGroupPlugin, item, hideGroups) + ) + item.find('[name=role_ids]').trigger('change') + item - # deselect conflicting roles - if checked - if role && role.preferences && role.preferences.not - for notRole in role.preferences.not - localRole = App.Role.findByAttribute('name', notRole) - if localRole - localElement = item.find("[name=role_ids][value=#{localRole.id}]") - 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}]") + @checkUncheck: (element, rolesWithGroupPlugin, item, hideGroups) -> + checked = element.prop('checked') + role_id = element.prop('value') + return if !role_id + role = App.Role.find(role_id) + return if !role + triggers = [] - # if role with groups plugin is deselected, hide group selection - if !checked - show = false - for role_id, group of rolesWithGroupPlugin - if item.find("[name=role_ids][value=#{role_id}]").prop('checked') - show = true - if !show - item.find('.js-groupList').addClass('hidden') + # deselect conflicting roles + if checked + if role && role.preferences && role.preferences.not + for notRole in role.preferences.not + localRole = App.Role.findByAttribute('name', notRole) + if localRole + localElement = item.find("[name=role_ids][value=#{localRole.id}]") + 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 hideGroups - item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', false) - return - - # if role with groups plugin is selected, show group selection - if rolesWithGroupPlugin[role_id] is 'group' - item.find('.js-groupList:not(.js-groupListHide)').removeClass('hidden') + # if role with groups plugin is deselected, hide group selection + if !checked + show = false + for role_id, group of rolesWithGroupPlugin + if item.find("[name=role_ids][value=#{role_id}]").prop('checked') + show = true + if !show + item.find('.js-groupList').addClass('hidden') # select groups if only one is available 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 - trigger.trigger('change') - ) + # if role with groups plugin is selected, show group selection + 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')