Reduced rerendering of object (introduction of App.ObserverController).

This commit is contained in:
Martin Edenhofer 2016-05-04 18:12:53 +02:00
parent dfc5900234
commit 08c5ca3e62
23 changed files with 292 additions and 225 deletions

View file

@ -260,7 +260,7 @@ class App.Controller extends Spine.Controller
# open ticket in new task if curent user agent
if @isRole('Agent')
@el.find('div.ticket-popover, span.ticket-popover').bind('click', (e) =>
@$('div.ticket-popover, span.ticket-popover').bind('click', (e) =>
id = $(e.target).data('id')
if id
ticket = App.Ticket.find(id)
@ -304,8 +304,7 @@ class App.Controller extends Spine.Controller
# open user in new task if current user is agent
return if !@isRole('Agent')
@el.find('div.user-popover, span.user-popover').bind('click', (e) =>
@$('div.user-popover, span.user-popover').bind('click', (e) =>
id = $(e.target).data('id')
if id
user = App.User.find(id)
@ -363,7 +362,7 @@ class App.Controller extends Spine.Controller
# open org in new task if current user agent
return if !@isRole('Agent')
@el.find('div.organization-popover, span.organization-popover').bind('click', (e) =>
@$('div.organization-popover, span.organization-popover').bind('click', (e) =>
id = $(e.target).data('id')
if id
organization = App.Organization.find(id)

View file

@ -1068,3 +1068,83 @@ class App.CollectionController extends App.Controller
onRemoved: (id) ->
# nothing
class App.ObserverController extends App.Controller
model: 'Ticket'
template: 'ticket_zoom/title'
###
observe:
title: true
observeNot:
title: true
###
constructor: ->
super
#console.trace()
@log 'debug', 'new', @object_id, @model
object = App[@model].fullLocal(@object_id)
if !object
App[@model].full(@object_id, @maybeRender)
else
@maybeRender(object)
# rerender, e. g. on language change
@bind('ui:rerender', =>
@lastAttributres = undefined
object = App[@model].fullLocal(@object_id)
@maybeRender(object)
)
subscribe: (object) =>
console.log('subscribe', object)
@maybeRender(object)
maybeRender: (object) =>
@log 'debug', 'maybeRender', @object_id, object, @model
if !@subscribeId
@subscribeId = object.subscribe(@subscribe)
# remember current attributes
currentAttributes = {}
if @observe
for key, active of @observe
if active
currentAttributes[key] = object[key]
if @observeNot
attributes = object.attributes()
for key, value of attributes
if !@observeNot[key]
currentAttributes[key] = value
if !@lastAttributres
@lastAttributres = {}
else
diff = difference(currentAttributes, @lastAttributres)
if _.isEmpty(diff)
@log 'debug', 'maybeRender no diff, no rerender'
return
@log 'debug', 'maybeRender.diff', diff
@lastAttributres = currentAttributes
@render(object)
render: (object) =>
@log 'debug', 'render', @template, object
@html App.view(@template)(
object: object
)
if @renderPost
@renderPost(object)
release: =>
#console.trace()
@log 'debug', 'release', @object_id, @model
App[@model].unsubscribe(@subscribeId)

View file

@ -60,7 +60,7 @@ class App.DashboardActivityStream extends App.Controller
))
new App.WidgetAvatar(
el: html.find('.js-avatar')
user_id: item.created_by_id
object_id: item.created_by_id
size: 40
)
html

View file

@ -126,7 +126,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
if App.Session.get('id')
new App.WidgetAvatar(
el: @$('.js-avatar')
user_id: App.Session.get('id')
object_id: App.Session.get('id')
type: 'personal'
)

View file

@ -50,7 +50,7 @@ class App.OrganizationProfile extends App.Controller
new Object(
el: elLocal.find('.js-object-container')
organization: organization
object_id: organization.id
task_key: @task_key
)
@ -65,19 +65,20 @@ class App.OrganizationProfile extends App.Controller
genericObject: organization
)
class Object extends App.Controller
class Object extends App.ObserverController
model: 'Organization'
observeNot:
created_at: true
created_by_id: true
updated_at: true
updated_by_id: true
preferences: true
source: true
image_source: true
events:
'focusout [contenteditable]': 'update'
constructor: (params) ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.Organization.full(@organization.id, @render, false, true)
release: =>
App.Organization.unsubscribe(@subscribeId)
render: (organization) =>
# update taskbar with new meta data
@ -111,6 +112,17 @@ class Object extends App.Controller
maxlength: 250
})
# show members
members = []
for userId in organization.member_ids
el = $('<div></div>')
new Member(
object_id: userId
el: el
)
members.push el
@$('.js-userList').html(members)
# start action controller
showHistory = ->
new App.OrganizationHistory(
@ -151,13 +163,26 @@ class Object extends App.Controller
update: (e) =>
name = $(e.target).attr('data-name')
value = $(e.target).html()
org = App.Organization.find(@organization.id)
org = App.Organization.find(@object_id)
if org[name] isnt value
@lastAttributres[name] = value
data = {}
data[name] = value
org.updateAttributes(data)
@log 'notice', 'update', name, value, org
class Member extends App.ObserverController
model: 'User'
observe:
firstname: true
lastname: true
login: true
email: true
render: (user) =>
@html App.view('organization_profile/member')(
user: user
)
class Router extends App.ControllerPermanent
constructor: (params) ->

View file

@ -300,14 +300,14 @@ class App.TicketZoom extends App.Controller
)
new App.TicketZoomTitle(
ticket: @ticket
object_id: @ticket.id
overview_id: @overview_id
el: elLocal.find('.ticket-title')
task_key: @task_key
)
new App.TicketZoomMeta(
ticket: @ticket
object_id: @ticket.id
el: elLocal.find('.ticket-meta')
)
@ -353,7 +353,7 @@ class App.TicketZoom extends App.Controller
el = @el
new App.WidgetAvatar(
el: el.find('.ticketZoom-header .js-avatar')
user_id: @ticket.customer_id
object_id: @ticket.customer_id
size: 50
)
@sidebar = new App.TicketZoomSidebar(

View file

@ -23,7 +23,7 @@ class App.TicketZoomArticleActions extends App.Controller
else
@html ''
publicInternal: (e) ->
publicInternal: (e) =>
e.preventDefault()
articleContainer = $(e.target).closest('.ticket-article-item')
article_id = $(e.target).parents('[data-id]').data('id')
@ -33,9 +33,8 @@ class App.TicketZoomArticleActions extends App.Controller
internal = true
if article.internal == true
internal = false
article.updateAttributes(
internal: internal
)
@lastAttributres.internal = internal
article.updateAttributes(internal: internal)
# runntime update
if internal

View file

@ -176,10 +176,9 @@ class App.TicketZoomArticleNew extends App.Controller
new App.WidgetAvatar(
el: @$('.js-avatar')
user_id: App.Session.get('id')
object_id: App.Session.get('id')
size: 40
position: 'right'
class: 'zIndex-5'
)
configure_attributes = [

View file

@ -1,7 +1,7 @@
class App.TicketZoomArticleView extends App.Controller
constructor: ->
super
@article_controller = {}
@articleController = {}
@run()
execute: (params) =>
@ -11,11 +11,12 @@ class App.TicketZoomArticleView extends App.Controller
run: =>
all = []
for ticket_article_id in @ticket_article_ids
if !@article_controller[ticket_article_id]
controllerKey = ticket_article_id.toString()
if !@articleController[controllerKey]
el = $('<div></div>')
@article_controller[ticket_article_id] = new ArticleViewItem(
@articleController[controllerKey] = new ArticleViewItem(
ticket: @ticket
ticket_article_id: ticket_article_id
object_id: ticket_article_id
el: el
ui: @ui
highligher: @highligher
@ -23,8 +24,26 @@ class App.TicketZoomArticleView extends App.Controller
all.push el
@el.append(all)
class ArticleViewItem extends App.Controller
hasChangedAttributes: ['from', 'to', 'cc', 'subject', 'body', 'preferences']
# check elements to remove
for article_id, controller of @articleController
exists = false
for localArticleId in @ticket_article_ids
if localArticleId.toString() is article_id.toString()
exists = true
if !exists
controller.remove()
delete @articleController[article_id.toString()]
class ArticleViewItem extends App.ObserverController
model: 'TicketArticle'
observe:
from: true
to: true
cc: true
subject: true
body: true
internal: true
preferences: true
elements:
'.textBubble-content': 'textBubbleContent'
@ -36,12 +55,7 @@ class ArticleViewItem extends App.Controller
'click .js-unfold': 'unfold'
constructor: ->
super
@seeMore = false
@force = true
@render()
# set expand of text area only once
@bind('ui::ticket::shown', (data) =>
@ -54,112 +68,75 @@ class ArticleViewItem extends App.Controller
@setSeeMore()
)
# rerender, e. g. on language change
@bind('ui:rerender', =>
@force = true
@render(undefined)
)
# subscribe to changes
@subscribeId = App.TicketArticle.full(@ticket_article_id, @render, false, true)
release: =>
App.TicketArticle.unsubscribe(@subscribeId)
super
setHighlighter: =>
return if @el.is(':hidden')
# use delay do no ui blocking
#@highligher.loadHighlights(@ticket_article_id)
#@highligher.loadHighlights(@object_id)
d = =>
@highligher.loadHighlights(@ticket_article_id)
@highligher.loadHighlights(@object_id)
@delay(d, 200)
hasChanged: (article) =>
# if no last article exists, remember it and return true
if !@articleAttributesLastUpdate
@articleAttributesLastUpdate = {}
for item in @hasChangedAttributes
@articleAttributesLastUpdate[item] = article[item]
return true
# compare last and current article attributes
articleAttributesLastUpdateCheck = {}
for item in @hasChangedAttributes
articleAttributesLastUpdateCheck[item] = article[item]
diff = difference(@articleAttributesLastUpdate, articleAttributesLastUpdateCheck)
return false if !diff || _.isEmpty( diff )
@articleAttributesLastUpdate = articleAttributesLastUpdateCheck
true
render: (article) =>
# get articles
@article = App.TicketArticle.fullLocal(@ticket_article_id)
# set @el attributes
if !article
@el.addClass("ticket-article-item #{@article.sender.name.toLowerCase()}")
@el.attr('data-id', @article.id)
@el.attr('id', "article-#{@article.id}")
# set internal change directly in dom, without rerender while article
if !article || ( @lastArticle && @lastArticle.internal isnt @article.internal )
if @article.internal is true
@el.addClass('is-internal')
else
@el.removeClass('is-internal')
# check if rerender is needed
if !@force && !@hasChanged(@article)
@lastArticle = @article.attributes()
return
@force = false
@el.addClass("ticket-article-item #{article.sender.name.toLowerCase()}")
@el.attr('data-id', article.id)
@el.attr('id', "article-#{article.id}")
# prepare html body
if @article.content_type is 'text/html'
if @article.sender.name is 'Agent'
@article['html'] = App.Utils.signatureIdentify(@article.body, false, true)
if article.content_type is 'text/html'
if article.sender.name is 'Agent'
article['html'] = App.Utils.signatureIdentify(article.body, false, true)
else
@article['html'] = App.Utils.signatureIdentify(@article.body)
article['html'] = App.Utils.signatureIdentify(article.body)
else
# client signature detection
bodyHtml = App.Utils.text2html(@article.body)
@article['html'] = App.Utils.signatureIdentify(bodyHtml)
bodyHtml = App.Utils.text2html(article.body)
article['html'] = App.Utils.signatureIdentify(bodyHtml)
# if no signature detected or within frist 25 lines, check if signature got detected in backend
if @article['html'] is bodyHtml || (@article.preferences && @article.preferences.signature_detection < 25)
if article['html'] is bodyHtml || (article.preferences && article.preferences.signature_detection < 25)
signatureDetected = false
body = @article.body
if @article.preferences && @article.preferences.signature_detection
body = article.body
if article.preferences && article.preferences.signature_detection
signatureDetected = '########SIGNATURE########'
# coffeelint: disable=no_unnecessary_double_quotes
body = body.split("\n")
body.splice(@article.preferences.signature_detection, 0, signatureDetected)
body.splice(article.preferences.signature_detection, 0, signatureDetected)
body = body.join("\n")
# coffeelint: enable=no_unnecessary_double_quotes
if signatureDetected
body = App.Utils.textCleanup(body)
@article['html'] = App.Utils.text2html(body)
@article['html'] = @article['html'].replace(signatureDetected, '<span class="js-signatureMarker"></span>')
article['html'] = App.Utils.text2html(body)
article['html'] = article['html'].replace(signatureDetected, '<span class="js-signatureMarker"></span>')
if article.sender.name is 'System'
@html App.view('ticket_zoom/article_view_system')(
ticket: @ticket
article: article
isCustomer: @isRole('Customer')
)
return
@html App.view('ticket_zoom/article_view')(
ticket: @ticket
article: @article
article: article
isCustomer: @isRole('Customer')
)
new App.WidgetAvatar(
el: @$('.js-avatar')
user_id: @article.created_by_id
object_id: article.created_by_id
size: 40
)
new App.TicketZoomArticleActions(
el: @$('.js-article-actions')
ticket: @ticket
article: @article
article: article
lastAttributres: @lastAttributres
)
# set see more
@ -343,3 +320,6 @@ class ArticleViewItem extends App.Controller
return false
return true
false
remove: =>
@el.remove()

View file

@ -1,24 +1,12 @@
class App.TicketZoomMeta extends App.Controller
constructor: ->
super
@render()
# rerender, e. g. on language change
@bind('ui:rerender', =>
@render()
)
class App.TicketZoomMeta extends App.ObserverController
model: 'Ticket'
observe:
number: true
created_at: true
escalation_time: true
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')
)
release: =>
App.Ticket.unsubscribe(@subscribeId)

View file

@ -1,34 +1,13 @@
class App.TicketZoomTitle extends App.Controller
class App.TicketZoomTitle extends App.ObserverController
model: 'Ticket'
template: 'ticket_zoom/title'
observe:
title: true
events:
'blur .ticket-title-update': 'update'
constructor: ->
super
@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)
@title = ticket.title
# check if render is needed
if @lastTitle && @lastTitle is ticket.title
return
@lastTitle = ticket.title
@html App.view('ticket_zoom/title')(
ticket: ticket
)
renderPost: (object) =>
@$('.ticket-title-update').ce({
mode: 'textonly'
multiline: false
@ -39,8 +18,8 @@ class App.TicketZoomTitle extends App.Controller
title = $(e.target).ceg() || ''
# update title
if title isnt @title
ticket = App.Ticket.find(@ticket.id)
return if title is @lastAttributres.title
ticket = App.Ticket.find(@object_id)
ticket.title = title
# reset article - should not be resubmited on next ticket update
@ -54,6 +33,3 @@ class App.TicketZoomTitle extends App.Controller
App.TaskManager.touch(@task_key)
App.Event.trigger('overview:fetch')
release: =>
App.Ticket.unsubscribe(@subscribeId)

View file

@ -50,7 +50,7 @@ class App.UserProfile extends App.Controller
new Object(
el: elLocal.find('.js-object-container')
user: user
object_id: user.id
task_key: @task_key
)
@ -65,19 +65,23 @@ class App.UserProfile extends App.Controller
genericObject: user
)
class Object extends App.Controller
class Object extends App.ObserverController
model: 'User'
observeNot:
created_at: true
created_by_id: true
updated_at: true
updated_by_id: true
preferences: true
password: true
last_login: true
login_failed: true
source: true
image_source: true
events:
'focusout [contenteditable]': 'update'
constructor: (params) ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.User.full(@user.id, @render, false, true)
release: =>
App.User.unsubscribe(@subscribeId)
render: (user) =>
# update taskbar with new meta data
@ -111,6 +115,12 @@ class Object extends App.Controller
maxlength: 250
})
if user.organization_id
new Organization(
object_id: user.organization_id
el: @$('.js-organization')
)
# start action controller
showHistory = =>
new App.UserHistory(
@ -151,13 +161,24 @@ class Object extends App.Controller
update: (e) =>
name = $(e.target).attr('data-name')
value = $(e.target).html()
user = App.User.find(@user.id)
user = App.User.find(@object_id)
if user[name] isnt value
@lastAttributres[name] = value
data = {}
data[name] = value
user.updateAttributes(data)
@log 'notice', 'update', name, value, user
class Organization extends App.ObserverController
model: 'Organization'
observe:
name: true
render: (organization) =>
@html App.view('user_profile/organization')(
organization: organization
)
class Router extends App.ControllerPermanent
constructor: (params) ->
super

View file

@ -1,15 +1,11 @@
class App.WidgetAvatar extends App.Controller
constructor: ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.User.full(@user_id, @render, false, true)
release: =>
App.User.unsubscribe(@subscribeId)
class App.WidgetAvatar extends App.ObserverController
model: 'User'
observe:
login: true
firstname: true
lastname: true
email: true
render: (user) =>
@html user.avatar @size, @position, undefined, false, false, @type
# start user popups
@html(user.avatar @size, @position, undefined, false, false, @type)
@userPopups(@position)

View file

@ -1,5 +1,5 @@
class App.Organization extends App.Model
@configure 'Organization', 'name', 'shared', 'note', 'active', 'updated_at'
@configure 'Organization', 'name', 'shared', 'note', 'member_ids', 'active', 'updated_at'
@extend Spine.Model.Ajax
@url: @apiPath + '/organizations'
@configure_attributes = [
@ -36,8 +36,8 @@ Mit **Organisationen** können Sie Kunden **gruppieren**. Dies hat u. a. zwei be
if data['member_ids']
data['members'] = []
for user_id in data['member_ids']
if App.User.exists( user_id )
user = App.User.find( user_id )
if App.User.exists(user_id)
user = App.User.find(user_id)
data['members'].push user
data

View file

@ -0,0 +1,4 @@
<div class="userList-entry">
<%- @user.avatar("40") %>
<a href="<%- @user.uiUrl() %>" class="userList-name user-popover" data-id="<%- @user.id %>"><%= @user.displayName() %></a>
</div>

View file

@ -27,13 +27,6 @@
<% if @organization.members: %>
<div class="profile-section profile-memberSection">
<label><%- @T('Members') %></label>
<div class="userList">
<% for user in @organization.members: %>
<div class="userList-entry">
<%- user.avatar("40") %>
<a href="<%- user.uiUrl() %>" class="userList-name user-popover" data-id="<%- user.id %>"><%= user.displayName() %></a>
</div>
<% end %>
</div>
<div class="userList js-userList"></div>
</div>
<% end %>

View file

@ -32,7 +32,7 @@
</div>
</div>
</div>
<div class="article-content zIndex-5 bubble-gap">
<div class="article-content bubble-gap">
<div class="internal-border">
<div class="input form-group">

View file

@ -29,7 +29,7 @@
</div>
</div>
<div class="article-content zIndex-1">
<div class="article-content">
<% if @article.sender.name isnt 'Agent': %>
<% position = 'left' %>
<% else: %>

View file

@ -0,0 +1,3 @@
<div class="small task-subline">
"<%= @article.subject %>" -&gt; "<%= @article.to %>"
</div>

View file

@ -1 +1 @@
<div contenteditable="true" class="ticket-title-update" data-placeholder="<%- @Ti('Enter Title...') %>"><%= @ticket.title %></div>
<div contenteditable="true" class="ticket-title-update" data-placeholder="<%- @Ti('Enter Title...') %>"><%= @object.title %></div>

View file

@ -3,7 +3,7 @@
<%- @user.avatar("80") %>
<h1><%= @user.displayName() %></h1>
<% if @user.organization: %>
<div class="profile-organization"><a href="<%- @user.organization.uiUrl() %>"><%= @user.organization.displayName() %></a></div>
<div class="profile-organization js-organization"></div>
<% end %>
</div>
<div class="profile-section">

View file

@ -0,0 +1 @@
<a href="<%- @organization.uiUrl() %>"><%= @organization.displayName() %></a>

View file

@ -23,7 +23,10 @@ class Observer::Ticket::ArticleChanges < ActiveRecord::Observer
end
# save ticket
return if !changed
if !changed
record.ticket.touch
return
end
record.ticket.save
end