From 9447a08e6e1cf19735fdc0de58c8d7f868dcc720 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 20 Nov 2015 10:47:41 +0100 Subject: [PATCH 1/3] Improved performance of large object tables. --- .../_application_controller_table.coffee | 19 ++-- app/assets/javascripts/app/index.coffee | 89 +++++++++---------- .../javascripts/app/lib/app_init/log.coffee | 17 +++- .../app/views/generic/table.jst.eco | 4 +- 4 files changed, 71 insertions(+), 58 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.coffee index 3daa7ea1d..3c9f6fa4b 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_table.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_table.coffee @@ -262,15 +262,16 @@ class App.ControllerTable extends App.Controller # get content table = App.view('generic/table')( - table_id: @table_id - header: @headers - objects: @objects - checkbox: @checkbox - radio: @radio - groupBy: @groupBy - class: @class - destroy: destroy - callbacks: @callbackAttributes + table_id: @table_id + header: @headers + attributes: attributes + objects: @objects + checkbox: @checkbox + radio: @radio + groupBy: @groupBy + class: @class + destroy: destroy + callbacks: @callbackAttributes ) # convert to jquery object diff --git a/app/assets/javascripts/app/index.coffee b/app/assets/javascripts/app/index.coffee index f7316faa4..6db4444ab 100644 --- a/app/assets/javascripts/app/index.coffee +++ b/app/assets/javascripts/app/index.coffee @@ -1,6 +1,5 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ - #= require_self #= require_tree ./lib/app_init #= require ./config.coffee @@ -10,10 +9,11 @@ #= require_tree ./lib/app_post class App extends Spine.Controller - @viewPrint: (object, attribute_name) -> - attributes = {} - if object.constructor.attributesGet - attributes = object.constructor.attributesGet() + @viewPrint: (object, attribute_name, attributes) -> + if !attributes + attributes = {} + if object.constructor.attributesGet + attributes = object.constructor.attributesGet() attribute_config = attributes[attribute_name] value = object[attribute_name] valueRef = undefined @@ -34,11 +34,9 @@ class App extends Spine.Controller if parts[0] && parts[1] && object[ parts[0] ] value = object[ parts[0] ][ parts[1] ] - #console.log('Pa', attribute_name, object, attribute_config, object[attribute_name], valueRef, value) - # if we have no config, get output this way if !attribute_config - return @viewPrintItem( value ) + return @viewPrintItem(value) # check if valueRef already exists, no lookup needed later if !valueRef @@ -48,10 +46,10 @@ class App extends Spine.Controller if object[attribute_name_without_ref] valueRef = object[attribute_name_without_ref] - return @viewPrintItem( value, attribute_config, valueRef ) + @viewPrintItem(value, attribute_config, valueRef) # define print name helper - @viewPrintItem: ( item, attribute_config = {}, valueRef ) -> + @viewPrintItem: (item, attribute_config = {}, valueRef) -> return '-' if item is undefined return '-' if item is '' return item if !item @@ -77,13 +75,13 @@ class App extends Spine.Controller # execute callback on content if attribute_config.callback - result = attribute_config.callback( result, attribute_config ) + result = attribute_config.callback(result, attribute_config) # text2html in textarea view isHtmlEscape = false if attribute_config.tag is 'textarea' isHtmlEscape = true - result = App.Utils.text2html( result ) + result = App.Utils.text2html(result) # remember, html snippets are already escaped else if attribute_config.tag is 'richtext' @@ -95,9 +93,9 @@ class App extends Spine.Controller result = attribute_config.options[result] # translate content - if attribute_config.translate || ( isObject && item.translate && item.translate() ) + if attribute_config.translate || (isObject && item.translate && item.translate()) isHtmlEscape = true - result = App.i18n.translateContent( result ) + result = App.i18n.translateContent(result) # transform date if attribute_config.tag is 'date' @@ -121,71 +119,71 @@ class App extends Spine.Controller result @view: (name) -> - template = ( params = {} ) -> + template = (params = {}) -> # define print name helper - params.P = ( object, attribute_name ) -> - App.viewPrint( object, attribute_name ) + params.P = (object, attribute_name, attributes) -> + App.viewPrint(object, attribute_name, attributes) # define date format helper - params.date = ( time ) -> + params.date = (time) -> return '' if !time timeObject = new Date(time) - d = App.Utils.formatTime( timeObject.getDate(), 2 ) - m = App.Utils.formatTime( timeObject.getMonth() + 1, 2 ) + d = App.Utils.formatTime(timeObject.getDate(), 2) + m = App.Utils.formatTime(timeObject.getMonth() + 1, 2) y = timeObject.getFullYear() "#{y}-#{m}-#{d}" # define datetime format helper - params.datetime = ( time ) -> + params.datetime = (time) -> return '' if !time timeObject = new Date(time) - d = App.Utils.formatTime( timeObject.getDate(), 2 ) - m = App.Utils.formatTime( timeObject.getMonth() + 1, 2 ) + d = App.Utils.formatTime(timeObject.getDate(), 2) + m = App.Utils.formatTime(timeObject.getMonth() + 1, 2) y = timeObject.getFullYear() - S = App.Utils.formatTime( timeObject.getSeconds(), 2 ) - M = App.Utils.formatTime( timeObject.getMinutes(), 2 ) - H = App.Utils.formatTime( timeObject.getHours(), 2 ) + S = App.Utils.formatTime(timeObject.getSeconds(), 2) + M = App.Utils.formatTime(timeObject.getMinutes(), 2) + H = App.Utils.formatTime(timeObject.getHours(), 2) "#{y}-#{m}-#{d} #{H}:#{M}:#{S}" # define decimal format helper - params.decimal = ( data, positions = 2 ) -> + params.decimal = (data, positions = 2) -> App.Utils.decimal(data, positions) # define translation helper - params.T = ( item, args... ) -> - App.i18n.translateContent( item, args... ) + params.T = (item, args...) -> + App.i18n.translateContent(item, args...) # define translation inline helper - params.Ti = ( item, args... ) -> - App.i18n.translateInline( item, args... ) + params.Ti = (item, args...) -> + App.i18n.translateInline(item, args...) # define translation for date helper - params.Tdate = ( item, args... ) -> - App.i18n.translateDate( item, args... ) + params.Tdate = (item, args...) -> + App.i18n.translateDate(item, args...) # define translation for timestamp helper - params.Ttimestamp = ( item, args... ) -> - App.i18n.translateTimestamp( item, args... ) + params.Ttimestamp = (item, args...) -> + App.i18n.translateTimestamp(item, args...) # define linkify helper - params.L = ( item ) -> + params.L = (item) -> if item && typeof item is 'string' - return App.Utils.linkify( item ) + return App.Utils.linkify(item) item # define config helper - params.C = ( key ) -> - App.Config.get( key ) + params.C = (key) -> + App.Config.get(key) # define session helper - params.S = ( key ) -> - App.Session.get( key ) + params.S = (key) -> + App.Session.get(key) # define address line helper - params.AddressLine = ( line ) -> + params.AddressLine = (line) -> return '' if !line items = emailAddresses.parseAddressList(line) @@ -202,15 +200,14 @@ class App extends Spine.Controller result = result + App.Utils.htmlEscape(item.name) + ' ' if item.address result = result + " <#{App.Utils.htmlEscape(item.address)}>" - result # define file size helper - params.humanFileSize = ( size ) -> + params.humanFileSize = (size) -> App.Utils.humanFileSize(size) # define pretty/human time helper - params.humanTime = ( time, escalation = false, cssClass = '') -> + params.humanTime = (time, escalation = false, cssClass = '') -> timestamp = App.i18n.translateTimestamp(time) if escalation cssClass += ' escalation' @@ -222,7 +219,7 @@ class App extends Spine.Controller App.Utils.icon(name, className) # define richtext helper - params.RichText = ( string ) -> + params.RichText = (string) -> if string.match(/@T\('/) string = string.replace(/@T\('(.+?)'\)/g, (match, capture) -> App.i18n.translateContent(capture) diff --git a/app/assets/javascripts/app/lib/app_init/log.coffee b/app/assets/javascripts/app/lib/app_init/log.coffee index b204a7b7d..59ae526fd 100644 --- a/app/assets/javascripts/app/lib/app_init/log.coffee +++ b/app/assets/javascripts/app/lib/app_init/log.coffee @@ -27,6 +27,11 @@ class App.Log _instance ?= new _Singleton _instance.config( type, regex ) + @timeTrack: (message) -> + if _instance == undefined + _instance ?= new _Singleton + _instance.timeTrack(message) + class _Singleton constructor: -> @moduleColorsMap = {} @@ -115,4 +120,14 @@ class _Singleton goldenRatio = 0.618033988749895 @hue += goldenRatio @hue = @hue % 1 - @hue * 360 \ No newline at end of file + @hue * 360 + + timeTrack: (message) => + currentTime = new Date().getTime() + if !@lastTime + @lastTime = currentTime + console.log('timeTrack start', message) + else + diff = currentTime - @lastTime + @lastTime = currentTime + console.log('timeTrack start', message, diff) diff --git a/app/assets/javascripts/app/views/generic/table.jst.eco b/app/assets/javascripts/app/views/generic/table.jst.eco index 49e61db49..deea88296 100644 --- a/app/assets/javascripts/app/views/generic/table.jst.eco +++ b/app/assets/javascripts/app/views/generic/table.jst.eco @@ -41,7 +41,7 @@ <% groupLast = '' %> <% for object in @objects: %> <% if @groupBy: %> - <% groupByName = @P( object, @groupBy ) %> + <% groupByName = @P( object, @groupBy, @attributes ) %> <% if groupLast isnt groupByName: %> <%= groupByName %> <% groupLast = groupByName %> @@ -68,7 +68,7 @@ <% end %> <% for item in @header: %> - <% value = @P( object, item.name ) %> + <% value = @P( object, item.name, @attributes ) %> <% if @callbacks: %> <% if item.name.substr(item.name.length-3, item.name.length) is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %> <% refObject = object[ item.name.substr(0, item.name.length-3) ] %> From da047e6286ff427b3f4060580440fbbdc7f02357 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 20 Nov 2015 10:49:22 +0100 Subject: [PATCH 2/3] Added inline translation support. --- .../app/controllers/ticket_zoom.coffee | 38 ++++++---------- .../ticket_zoom/article_new.coffee | 43 ++++++++++--------- .../ticket_zoom/article_view.coffee | 23 +++++----- .../ticket_zoom/attribute_bar.coffee | 5 +++ .../app/controllers/ticket_zoom/meta.coffee | 14 ++++-- .../app/controllers/ticket_zoom/title.coffee | 28 ++++++++---- 6 files changed, 84 insertions(+), 67 deletions(-) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index 5d388b32e..87b35f944 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -40,33 +40,23 @@ class App.TicketZoom extends App.Controller @interval(update, 1800000, 'pull_check') # fetch new data if triggered - @bind( - 'Ticket:update Ticket:touch' - (data) => + @bind('Ticket:update Ticket:touch', (data) => - # check if current ticket has changed - return if data.id.toString() isnt @ticket_id.toString() + # check if current ticket has changed + return if data.id.toString() isnt @ticket_id.toString() - # check if we already have the request queued - #@log 'notice', 'TRY', @ticket_id, new Date(data.updated_at), new Date(@ticketUpdatedAtLastCall) - update = => - @fetch( @ticket_id, false ) - if !@ticketUpdatedAtLastCall || ( new Date(data.updated_at).toString() isnt new Date(@ticketUpdatedAtLastCall).toString() ) - @delay( update, 1200, 'ticket-zoom-' + @ticket_id ) + # check if we already have the request queued + #@log 'notice', 'TRY', @ticket_id, new Date(data.updated_at), new Date(@ticketUpdatedAtLastCall) + update = => + @fetch(@ticket_id, false) + if !@ticketUpdatedAtLastCall || ( new Date(data.updated_at).toString() isnt new Date(@ticketUpdatedAtLastCall).toString() ) + @delay( update, 1200, 'ticket-zoom-' + @ticket_id ) ) # rerender view, e. g. on langauge change - @bind 'ui:rerender', => - return if !@authenticate(true) - - # reset controllers state vars - @shown = false - @initDone = false - @activeState = false - @renderDone = false - - # rerender view - @render() + @bind('ui:rerender', => + @fetch(@ticket_id, true) + ) meta: => @@ -343,7 +333,7 @@ class App.TicketZoom extends App.Controller ticket_id: @ticket.id ) - @article_view = new App.TicketZoomArticleView( + @articleView = new App.TicketZoomArticleView( ticket: @ticket el: @$('.ticket-article') ui: @ @@ -369,7 +359,7 @@ class App.TicketZoom extends App.Controller ) # show article - @article_view.execute( + @articleView.execute( ticket_article_ids: @ticket_article_ids ) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee index 38a7d3d48..3045db8a4 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee @@ -93,31 +93,32 @@ class App.TicketZoomArticleNew extends App.Controller @openTextarea(null, true) # set article type and expand text area - @bind( - 'ui::ticket::setArticleType' - (data) => - return if data.ticket.id isnt @ticket_id - #@setArticleType(data.type.name) + @bind('ui::ticket::setArticleType', (data) => + return if data.ticket.id isnt @ticket_id + #@setArticleType(data.type.name) - @openTextarea(null, true) - for key, value of data.article - if key is 'body' - @$('[data-name="' + key + '"]').html(value) - else - @$('[name="' + key + '"]').val(value) + @openTextarea(null, true) + for key, value of data.article + if key is 'body' + @$('[data-name="' + key + '"]').html(value) + else + @$('[name="' + key + '"]').val(value) - # preselect article type - @setArticleType('email') + # preselect article type + @setArticleType('email') ) # reset new article screen - @bind( - 'ui::ticket::taskReset' - (data) => - return if data.ticket_id isnt @ticket_id - @type = 'note' - @defaults = {} - @render() + @bind('ui::ticket::taskReset', (data) => + return if data.ticket_id isnt @ticket_id + @type = 'note' + @defaults = {} + @render() + ) + + # rerender, e. g. on language change + @bind('ui:rerender', => + @render() ) isIE10: -> @@ -135,7 +136,7 @@ class App.TicketZoomArticleNew extends App.Controller render: -> - ticket = App.Ticket.fullLocal( @ticket_id ) + ticket = App.Ticket.fullLocal(@ticket_id) @html App.view('ticket_zoom/article_new')( ticket: ticket diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee index fd0aa00fe..7133018be 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee @@ -40,16 +40,19 @@ class ArticleViewItem extends App.Controller @render() # set expand of text area only once - @bind( - 'ui::ticket::shown' - (data) => - return if data.ticket_id.toString() isnt @ticket.id.toString() + @bind('ui::ticket::shown', (data) => + return if data.ticket_id.toString() isnt @ticket.id.toString() - # set highlighter - @setHighlighter() + # set highlighter + @setHighlighter() - # set see more - @setSeeMore() + # set see more + @setSeeMore() + ) + + # rerender, e. g. on language change + @bind('ui:rerender', => + @render(undefined, true) ) # subscribe to changes @@ -84,7 +87,7 @@ class ArticleViewItem extends App.Controller @articleAttributesLastUpdate = articleAttributesLastUpdateCheck true - render: (article) => + render: (article, force = false) => # get articles @article = App.TicketArticle.fullLocal( @ticket_article_id ) @@ -103,7 +106,7 @@ class ArticleViewItem extends App.Controller @el.removeClass('is-internal') # check if rerender is needed - if !@hasChanged(@article) + if !force && !@hasChanged(@article) @lastArticle = @article.attributes() return diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee index b6dce1c03..c13687d92 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee @@ -19,6 +19,11 @@ class App.TicketZoomAttributeBar extends App.Controller @subscribeId = App.Macro.subscribe(@render) @render() + # rerender, e. g. on language change + @bind('ui:rerender', => + @render() + ) + release: => App.Macro.unsubscribe(@subscribeId) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee index edfe2ba71..715147886 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee @@ -1,12 +1,20 @@ class App.TicketZoomMeta extends App.Controller constructor: -> super + @render() - @ticket = App.Ticket.fullLocal(@ticket.id) - @render(@ticket) - @subscribeId = @ticket.subscribe(@render) + # rerender, e. g. on language change + @bind('ui:rerender', => + @render() + ) render: (ticket) => + if !ticket + ticket = App.Ticket.fullLocal(@ticket.id) + + if !@subscribeId + @subscribeId = @ticket.subscribe(@render) + @html App.view('ticket_zoom/meta')( ticket: ticket isCustomer: @isRole('Customer') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee index a450f77b3..b44ef7bd8 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee @@ -4,12 +4,21 @@ class App.TicketZoomTitle extends App.Controller constructor: -> super + @render() - @ticket = App.Ticket.fullLocal( @ticket.id ) - @subscribeId = @ticket.subscribe(@render) - @render(@ticket) + # rerender, e. g. on language change + @bind('ui:rerender', => + @render() + ) render: (ticket) => + if !ticket + ticket = App.Ticket.fullLocal(@ticket.id) + + if !@subscribeId + @subscribeId = @ticket.subscribe(@render) + + @title = ticket.title # check if render is needed if @lastTitle && @lastTitle is ticket.title @@ -30,18 +39,19 @@ class App.TicketZoomTitle extends App.Controller title = $(e.target).ceg() || '' # update title - if title isnt @ticket.title - @ticket.title = title + if title isnt @title + ticket = App.Ticket.find(@ticket.id) + ticket.title = title # reset article - should not be resubmited on next ticket update - @ticket.article = undefined + ticket.article = undefined - @ticket.save() + ticket.save() - App.TaskManager.mute( @task_key ) + App.TaskManager.mute(@task_key) # update taskbar with new meta data App.Event.trigger 'task:render' release: => - App.Ticket.unsubscribe( @subscribeId ) \ No newline at end of file + App.Ticket.unsubscribe(@subscribeId) From 28dcee225d495f8c0a2210d2126b8c7599ba9270 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 20 Nov 2015 10:53:56 +0100 Subject: [PATCH 3/3] Fixed inline translations with &, < and > issues. --- .../app/controllers/widget/translation_inline.coffee | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/app/controllers/widget/translation_inline.coffee b/app/assets/javascripts/app/controllers/widget/translation_inline.coffee index 3ddb65a8e..a6eeaa8d1 100644 --- a/app/assets/javascripts/app/controllers/widget/translation_inline.coffee +++ b/app/assets/javascripts/app/controllers/widget/translation_inline.coffee @@ -34,7 +34,7 @@ class Widget extends App.Controller $('body') .on 'focus.translation', '.translation', (e) -> element = $(e.target) - element.data 'before', element.html() + element.data 'before', element.text() element .on 'blur.translation', '.translation', (e) => console.log('blur') @@ -42,12 +42,7 @@ class Widget extends App.Controller source = element.attr('title') # get new translation - translation_new = element.html() - translation_new = ('' + translation_new) - .replace(/<.+?>/g, '') - - # set new translation - element.html(translation_new) + translation_new = element.text() # update translation return if element.data('before') is translation_new @@ -58,7 +53,7 @@ class Widget extends App.Controller App.i18n.setMap(source, translation_new) # replace rest in page - $(".translation[title='#{source}']").html(translation_new) + $(".translation[title='#{source}']").text(translation_new) # update permanent translation mapString translation = App.Translation.findByAttribute('source', source)