From eb73b8b9accca06d84499b5552127116c422f8f0 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 17:07:27 +0200 Subject: [PATCH 01/12] Fixed controller name. --- config/routes/text_module.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/routes/text_module.rb b/config/routes/text_module.rb index e6548fdfc..542ff1875 100644 --- a/config/routes/text_module.rb +++ b/config/routes/text_module.rb @@ -2,11 +2,11 @@ module ExtraRoutes def add(map) # roles - map.match '/api/text_modules', :to => 'text_modules#index', :via => :get - map.match '/api/text_modules/:id', :to => 'text_modules#show', :via => :get - map.match '/api/text_modules', :to => 'text_modules#create', :via => :post - map.match '/api/text_modules/:id', :to => 'text_modules#update', :via => :put - map.match '/api/text_modules/:id', :to => 'templates#destroy', :via => :delete + map.match '/api/text_modules', :to => 'text_modules#index', :via => :get + map.match '/api/text_modules/:id', :to => 'text_modules#show', :via => :get + map.match '/api/text_modules', :to => 'text_modules#create', :via => :post + map.match '/api/text_modules/:id', :to => 'text_modules#update', :via => :put + map.match '/api/text_modules/:id', :to => 'text_modules#destroy', :via => :delete end module_function :add From 327d64801e169fe05687ba46df93a909f80808af Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 17:07:45 +0200 Subject: [PATCH 02/12] Prepared fror rails 4. --- config/routes/ticket.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/routes/ticket.rb b/config/routes/ticket.rb index bbea66ddf..50b4a72fe 100644 --- a/config/routes/ticket.rb +++ b/config/routes/ticket.rb @@ -12,7 +12,7 @@ module ExtraRoutes map.match '/api/ticket_history/:id', :to => 'tickets#ticket_history', :via => :get map.match '/api/ticket_customer', :to => 'tickets#ticket_customer', :via => :get map.match '/api/ticket_merge_list/:ticket_id', :to => 'tickets#ticket_merge_list', :via => :get - map.match '/api/ticket_merge/:slave_ticket_id/:master_ticket_number', :to => 'tickets#ticket_merge' + map.match '/api/ticket_merge/:slave_ticket_id/:master_ticket_number', :to => 'tickets#ticket_merge', :via => :get # ticket overviews map.match '/api/ticket_overviews', :to => 'ticket_overviews#show', :via => :get @@ -34,10 +34,10 @@ module ExtraRoutes map.match '/api/ticket_articles/:id', :to => 'ticket_articles#show', :via => :get map.match '/api/ticket_articles', :to => 'ticket_articles#create', :via => :post map.match '/api/ticket_articles/:id', :to => 'ticket_articles#update', :via => :put - map.match '/api/ticket_attachment/:ticket_id/:article_id/:id', :to => 'ticket_articles#attachment' - map.match '/api/ticket_attachment_new', :to => 'ticket_articles#attachment_new' + map.match '/api/ticket_attachment/:ticket_id/:article_id/:id', :to => 'ticket_articles#attachment', :via => :get + map.match '/api/ticket_attachment_new', :to => 'ticket_articles#attachment_new', :via => :post map.match '/api/ticket_article_plain/:id', :to => 'ticket_articles#article_plain', :via => :get end module_function :add -end \ No newline at end of file +end From 0270950044e6d1f4798a89c36a76482834fbecfd Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 17:09:08 +0200 Subject: [PATCH 03/12] Prepared for rails 4. --- db/migrate/20120101000020_create_network.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/migrate/20120101000020_create_network.rb b/db/migrate/20120101000020_create_network.rb index cc7130721..d963d1661 100644 --- a/db/migrate/20120101000020_create_network.rb +++ b/db/migrate/20120101000020_create_network.rb @@ -19,7 +19,7 @@ class CreateNetwork < ActiveRecord::Migration t.timestamps end add_index :network_category_types, [:name], :unique => true - + create_table :network_privacies do |t| t.column :name, :string, :limit => 100, :null => false t.column :key, :string, :limit => 250, :null => false @@ -51,7 +51,7 @@ class CreateNetwork < ActiveRecord::Migration create_table :network_items do |t| t.references :network_category, :null => false t.column :title, :string, :limit => 200, :null => false - t.column :body, :string, :limit => 25000, :null => false + t.column :body, :string, :limit => 20000, :null => false t.column :updated_by_id, :integer, :null => false t.column :created_by_id, :integer, :null => false t.timestamps @@ -60,13 +60,13 @@ class CreateNetwork < ActiveRecord::Migration create_table :network_item_comments do |t| t.references :network_item, :null => false - t.column :body, :string, :limit => 25000, :null => false + t.column :body, :string, :limit => 20000, :null => false t.column :updated_by_id, :integer, :null => false t.column :created_by_id, :integer, :null => false t.timestamps end add_index :network_item_comments, [:network_item_id] - + create_table :network_item_plus do |t| t.references :network_item, :null => false t.column :updated_by_id, :integer, :null => false @@ -90,7 +90,7 @@ class CreateNetwork < ActiveRecord::Migration t.timestamps end add_index :network_item_subscriptions, [:network_item_id, :created_by_id], :unique => true, :name => 'index_network_item_subscriptions_on_item_id_and_created_by_id' - + end def down From 1c9776fcb433309dd3d1ee72f311860538c5f404 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 17:11:34 +0200 Subject: [PATCH 04/12] Prepard for rails 4. --- config/routes/tag.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/routes/tag.rb b/config/routes/tag.rb index e6a615ace..dc40d1727 100644 --- a/config/routes/tag.rb +++ b/config/routes/tag.rb @@ -2,10 +2,10 @@ module ExtraRoutes def add(map) # links - map.match '/api/tags', :to => 'tags#list' - map.match '/api/tags/add', :to => 'tags#add' - map.match '/api/tags/remove', :to => 'tags#remove' + map.match '/api/tags', :to => 'tags#list', :via => :get + map.match '/api/tags/add', :to => 'tags#add', :via => :get + map.match '/api/tags/remove', :to => 'tags#remove', :via => :get end module_function :add -end \ No newline at end of file +end From 5d7065a30b0a5233670b825127c8c884fdd69205 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 17:12:03 +0200 Subject: [PATCH 05/12] Prepared for rails 4. --- config/routes/link.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/routes/link.rb b/config/routes/link.rb index 89b7faeb0..086f413c6 100644 --- a/config/routes/link.rb +++ b/config/routes/link.rb @@ -2,10 +2,10 @@ module ExtraRoutes def add(map) # links - map.match '/api/links', :to => 'links#index' - map.match '/api/links/add', :to => 'links#add' - map.match '/api/links/remove', :to => 'links#remove' + map.match '/api/links', :to => 'links#index', :via => :get + map.match '/api/links/add', :to => 'links#add', :via => :get + map.match '/api/links/remove', :to => 'links#remove', :via => :get end module_function :add -end \ No newline at end of file +end From bbcb766e28d5b831dd92c46d0b67610b6abc50af Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 18:03:59 +0200 Subject: [PATCH 06/12] Prepared for rails 4. --- config/routes.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index b4e6d3154..c22f52dc3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,12 @@ Zammad::Application.routes.draw do # app init - match '/init', :to => 'init#index' - match '/app', :to => 'init#index' + match '/init', :to => 'init#index', :via => :get + match '/app', :to => 'init#index', :via => :get # You can have the root of your site routed with "root" # just remember to delete public/index.html. - root :to => 'init#index' + root :to => 'init#index', :via => :get # load routes from external files dir = File.expand_path('../', __FILE__) From bce032413933b710644e26c75ee6d58cc966a80d Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 18:05:50 +0200 Subject: [PATCH 07/12] Removed not needed gems. --- Gemfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Gemfile b/Gemfile index 04c907f93..291107dca 100644 --- a/Gemfile +++ b/Gemfile @@ -24,11 +24,6 @@ group :assets do gem 'uglifier', '>= 1.2.3' end -gem 'jquery-rails' - -# Optional support for eco templates -gem 'eco' - gem 'omniauth' gem 'omniauth-twitter' gem 'omniauth-facebook' From 5ee16d758be1af60c44077b3367cea374aacf0d9 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 29 May 2013 18:09:02 +0200 Subject: [PATCH 08/12] Raise exception if save or update failed. --- app/controllers/application_controller.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 791616002..b4d5fe246 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -213,9 +213,11 @@ class ApplicationController < ActionController::Base generic_object = object.new( object.param_cleanup(params) ) # save object - generic_object.save + generic_object.save! + model_create_render_item(generic_object) rescue Exception => e + puts e.message.inspect logger.error e.message render :json => { :error => e.message }, :status => :unprocessable_entity end @@ -231,7 +233,7 @@ class ApplicationController < ActionController::Base generic_object = object.find( params[:id] ) # save object - generic_object.update_attributes( object.param_cleanup(params) ) + generic_object.update_attributes!( object.param_cleanup(params) ) model_update_render_item(generic_object) rescue Exception => e logger.error e.message From b8a1ccd9e10a394e8b2607241a1375ca8f9b915d Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 30 May 2013 00:25:04 +0200 Subject: [PATCH 09/12] Added init version of auto save. --- .../controllers/agent_ticket_create.js.coffee | 19 +++++- .../controllers/agent_ticket_zoom.js.coffee | 53 +++++++++++------ .../app/controllers/task_widget.js.coffee | 2 - .../app/lib/app_post/task_manager.js.coffee | 58 +++++++++++++++++-- 4 files changed, 106 insertions(+), 26 deletions(-) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee index 092f38a09..8a5b5e1e9 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -49,6 +49,9 @@ class App.TicketCreate extends App.Controller @log 'AgentTicketPhone', 'error', defaults @render(defaults) + # start auto save + @autosave() + meta: => text = App.i18n.translateInline( @article_attributes['title'] ) subject = @el.find('[name=subject]').val() @@ -66,7 +69,7 @@ class App.TicketCreate extends App.Controller activate: => @navupdate '#' @title @article_attributes['title'] - + changed: => formCurrent = @formParam( @el.find('.ticket-create') ) diff = difference( @formDefault, formCurrent ) @@ -76,6 +79,18 @@ class App.TicketCreate extends App.Controller release: => # @clearInterval( @key, 'ticket_zoom' ) @el.remove() + @clearInterval( @id, @auto_save_key ) + + autosave: => + @auto_save_key = 'create' + @type + @id + update = => + data = @formParam( @el.find('.ticket-create') ) + diff = difference( @autosaveLast, data ) + if !@autosaveLast || ( diff && !_.isEmpty( diff ) ) + @autosaveLast = data + console.log('form hash changed', diff, data) + App.TaskManager.update( @task_key, { 'state': data }) + @interval( update, 10000, @id, @auto_save_key ) # get data / in case also ticket data for split fetch: (params) -> @@ -135,7 +150,7 @@ class App.TicketCreate extends App.Controller render: (template = {}) -> # set defaults - defaults = template['options'] || {} + defaults = template['options'] || @form_state || {} if !( 'ticket_state_id' of defaults ) defaults['ticket_state_id'] = App.Collection.findByAttribute( 'TicketState', 'name', 'open' ).id if !( 'ticket_priority_id' of defaults ) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee index 93399684e..aedaae759 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee @@ -34,6 +34,9 @@ class App.TicketZoom extends App.Controller @fetch( @ticket_id, false) @interval( update, 30000, @key, 'ticket_zoom' ) + # start auto save + @autosave() + meta: => return if !@ticket meta = @@ -62,6 +65,17 @@ class App.TicketZoom extends App.Controller @clearInterval( @key, 'ticket_zoom' ) @el.remove() + autosave: => + @auto_save_key = 'zoom' + @id + update = => + data = @formParam( @el.find('.ticket-update') ) + diff = difference( @autosaveLast, data ) + if !@autosaveLast || ( diff && !_.isEmpty( diff ) ) + @autosaveLast = data + console.log('form hash changed', diff, data) + App.TaskManager.update( @task_key, { 'state': data }) + @interval( update, 10000, @id, @auto_save_key ) + fetch: (ticket_id, force) -> return if !@Session.all() @@ -141,34 +155,36 @@ class App.TicketZoom extends App.Controller for article in @articles new Article( article: article ) + defaults = @form_state || {} + @configure_attributes_ticket = [ - { name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left' }, - { name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left' }, - { name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: true, relation: 'Group', filter: @edit_form, class: 'span2', item_class: 'pull-left' }, - { name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, relation: 'User', filter: @edit_form, nulloption: true, class: 'span2', item_class: 'pull-left' }, + { name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left', default: defaults['ticket_state_id'] }, + { name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left', default: defaults['ticket_priority_id'] }, + { name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: true, relation: 'Group', filter: @edit_form, class: 'span2', item_class: 'pull-left', default: defaults['group_id'] }, + { name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, relation: 'User', filter: @edit_form, nulloption: true, class: 'span2', item_class: 'pull-left', default: defaults['owner_id'] }, ] if @isRole('Customer') @configure_attributes_ticket = [ - { name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left' }, - { name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left' }, + { name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left', default: defaults['ticket_state_id'] }, + { name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', filter: @edit_form, translate: true, class: 'span2', item_class: 'pull-left', default: defaults['ticket_priority_id'] }, ] @configure_attributes_article = [ - { name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', filter: @edit_form, default: '9', translate: true, class: 'medium', item_class: '' }, - { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'in_reply_to', display: 'In Reply to', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'body', display: 'Text', tag: 'textarea', rows: 6, limit: 100, null: true, class: 'span7', item_class: '', upload: true }, - { name: 'internal', display: 'Visability', tag: 'select', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '' }, + { name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', filter: @edit_form, default: '9', translate: true, class: 'medium', item_class: '', default: defaults['ticket_article_type_id'] }, + { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['to'] }, + { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['cc'] }, + { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['subject'] }, + { name: 'in_reply_to', display: 'In Reply to', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['in_reply_to'] }, + { name: 'body', display: 'Text', tag: 'textarea', rows: 6, limit: 100, null: true, class: 'span7', item_class: '', upload: true, default: defaults['body'] }, + { name: 'internal', display: 'Visability', tag: 'select', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '', default: defaults['internal'] }, ] if @isRole('Customer') @configure_attributes_article = [ - { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'in_reply_to', display: 'In Reply to', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, - { name: 'body', display: 'Text', tag: 'textarea', rows: 6, limit: 100, null: true, class: 'span7', item_class: '', upload: true }, + { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['to'] }, + { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['cc'] }, + { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['subject'] }, + { name: 'in_reply_to', display: 'In Reply to', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide', default: defaults['in_reply_to'] }, + { name: 'body', display: 'Text', tag: 'textarea', rows: 6, limit: 100, null: true, class: 'span7', item_class: '', upload: true, default: defaults['body'] }, ] @html App.view('agent_ticket_zoom')( @@ -623,6 +639,7 @@ class TicketZoomRouter extends App.ControllerPermanent constructor: (params) -> super @log 'zoom router', params + # cleanup params clean_params = ticket_id: params.ticket_id diff --git a/app/assets/javascripts/app/controllers/task_widget.js.coffee b/app/assets/javascripts/app/controllers/task_widget.js.coffee index de52791b8..3e345d870 100644 --- a/app/assets/javascripts/app/controllers/task_widget.js.coffee +++ b/app/assets/javascripts/app/controllers/task_widget.js.coffee @@ -15,8 +15,6 @@ class App.TaskWidget extends App.Controller App.TaskManager.reset() @el.html('') - App.TaskManager.syncInitial() - sync = => App.TaskManager.sync() @delay( sync, 3000, 'task-widget' ) diff --git a/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee b/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee index 892170bf2..d43aca9c8 100644 --- a/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee @@ -9,16 +9,21 @@ class App.TaskManager _instance ?= new _Singleton _instance.all() - @add: ( type, type_id, callback, params, to_not_show ) -> + @add: ( type, type_id, callback, params, to_not_show, state ) -> if _instance == undefined _instance ?= new _Singleton - _instance.add( type, type_id, callback, params, to_not_show ) + _instance.add( type, type_id, callback, params, to_not_show, state ) @get: ( key ) -> if _instance == undefined _instance ?= new _Singleton _instance.get( key ) + @update: ( key, params ) -> + if _instance == undefined + _instance ?= new _Singleton + _instance.update( key, params ) + @remove: ( key ) -> if _instance == undefined _instance ?= new _Singleton @@ -39,6 +44,11 @@ class App.TaskManager _instance ?= new _Singleton _instance.syncTasksInitial() + @syncSave: -> + if _instance == undefined + _instance ?= new _Singleton + _instance.syncSave() + @sync: -> if _instance == undefined _instance ?= new _Singleton @@ -50,11 +60,12 @@ class _Singleton extends App.Controller constructor: -> @tasks = {} @task_count = 0 + @syncTasksInitial() all: -> @tasks - add: ( type, type_id, callback, params, to_not_show = false ) -> + add: ( type, type_id, callback, params, to_not_show = false, state ) -> for key, task of @tasks if task.type is type && task.type_id is type_id return key if to_not_show @@ -99,6 +110,13 @@ class _Singleton extends App.Controller params_app = _.clone(params) params_app['el'] = $('#content_permanent_' + @task_count ) params_app['task_key'] = @task_count + + # check if we have old state there + if !state + oldTask = @get_by_type( type, type_id ) + if oldTask + state = oldTask.state + params_app['form_state'] = state if to_not_show params_app['doNotLog'] = 1 a = new App[callback]( params_app ) @@ -128,6 +146,12 @@ class _Singleton extends App.Controller get: ( key ) => return @tasks[key] + update: ( key, params ) => + return false if !@tasks[key] + for item, value of params + @tasks[key][item] = value + @syncSave() + remove: ( key, to_not_show = false ) => if @tasks[key] @tasks[key].worker.release() @@ -143,6 +167,11 @@ class _Singleton extends App.Controller @tasks = {} App.Event.trigger 'ui:rerender' + get_by_type: (type, type_id) => + store = @syncLoad() || [] + for item in store + return item if item.type is type && item.type_id is type_id + syncAdd: (task) => store = @syncLoad() || [] for item in store @@ -163,6 +192,27 @@ class _Singleton extends App.Controller storeNew.push item App.Store.write( 'tasks', storeNew ) + syncSave: => + store = @syncLoad() || [] + storeNew = [] + for item in store + for key, task of @tasks + if task.type is item.type && task.type_id is item.type_id + console.log('MATCH', item) + if @tasks[key]['state'] + item['state'] = @tasks[key]['state'] + storeNew.push item +# if @tasks[key] +# @tasks[key].worker.release() +# +# storeNew.push item +# item = +# type: task.type +# type_id: task.type_id +# params: task.params +# callback: task.callback + App.Store.write( 'tasks', storeNew ) + syncLoad: => App.Store.get( 'tasks' ) @@ -176,7 +226,7 @@ class _Singleton extends App.Controller @delay( => task = store.shift() - @add(task.type, task.type_id, task.callback, task.params, true) + @add(task.type, task.type_id, task.callback, task.params, true, task.state) task_count * 500 ) From 2dd3853e9655a8ac6e326beacd1ef4221d1001c1 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 31 May 2013 01:33:20 +0200 Subject: [PATCH 10/12] Added eco gem. --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 291107dca..fc071a547 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ gem 'rails', '3.2.13' #gem 'rails-observers' #gem 'activerecord-session_store' +gem 'eco' + # Bundle edge Rails instead: #gem 'rails', :git => 'git://github.com/rails/rails.git' From 6f278fa8f72193b797d9156afab7f253da5f1d70 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 31 May 2013 13:29:48 +0200 Subject: [PATCH 11/12] Rewritten taskbar manager. --- .../controllers/agent_ticket_create.js.coffee | 4 +- .../controllers/agent_ticket_merge.js.coffee | 2 +- .../controllers/agent_ticket_zoom.js.coffee | 9 +- .../app/controllers/task_widget.js.coffee | 49 +-- .../app/lib/app_post/task_manager.js.coffee | 300 ++++++++---------- .../javascripts/app/views/task_widget.jst.eco | 4 +- app/assets/javascripts/application.js | 1 + 7 files changed, 173 insertions(+), 196 deletions(-) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee index 8a5b5e1e9..fe91d8159 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -89,7 +89,7 @@ class App.TicketCreate extends App.Controller if !@autosaveLast || ( diff && !_.isEmpty( diff ) ) @autosaveLast = data console.log('form hash changed', diff, data) - App.TaskManager.update( @task_key, { 'state': data }) + App.TaskManager.update( 'TicketCreateScreen', @type + '-' + @id, { 'state': data }) @interval( update, 10000, @id, @auto_save_key ) # get data / in case also ticket data for split @@ -302,7 +302,7 @@ class App.TicketCreate extends App.Controller # create new create screen # ui.render() - App.TaskManager.remove( ui.task_key ) + App.TaskManager.remove( 'TicketCreateScreen', ui.type + '-' + ui.id ) # scroll to top ui.scrollTo() diff --git a/app/assets/javascripts/app/controllers/agent_ticket_merge.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_merge.js.coffee index 42c09b701..b2255d688 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_merge.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_merge.js.coffee @@ -133,7 +133,7 @@ class App.TicketMerge extends App.ControllerModal msg: App.i18n.translateContent( 'Ticket %s merged!', data.slave_ticket['number'] ), timeout: 4000, - App.TaskManager.remove( @task_key ) + App.TaskManager.remove( 'Ticket', data.slave_ticket['id'] ) else diff --git a/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee index aedaae759..1ad04e10c 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee @@ -11,7 +11,7 @@ class App.TicketZoom extends App.Controller constructor: (params) -> super - @log 'zoom', params +# console.log 'zoom', params # check authentication return if !@authenticate() @@ -31,7 +31,7 @@ class App.TicketZoom extends App.Controller if cache @load(cache) update = => - @fetch( @ticket_id, false) + @fetch( @ticket_id, false ) @interval( update, 30000, @key, 'ticket_zoom' ) # start auto save @@ -73,7 +73,7 @@ class App.TicketZoom extends App.Controller if !@autosaveLast || ( diff && !_.isEmpty( diff ) ) @autosaveLast = data console.log('form hash changed', diff, data) - App.TaskManager.update( @task_key, { 'state': data }) + App.TaskManager.update( 'Ticket', @ticket_id, { 'state': data }) @interval( update, 10000, @id, @auto_save_key ) fetch: (ticket_id, force) -> @@ -93,7 +93,7 @@ class App.TicketZoom extends App.Controller return if _.isEqual( @dataLastCall.ticket, data.ticket) diff = difference( @dataLastCall.ticket, data.ticket ) console.log('diff', diff) - App.TaskManager.notify(@task_key) + App.TaskManager.notify( 'Ticket', @ticket_id ) if $('[name="body"]').val() App.Event.trigger 'notify', { type: 'success' @@ -293,7 +293,6 @@ class App.TicketZoom extends App.Controller internal = true if article.internal == true internal = false - article.updateAttributes( internal: internal ) diff --git a/app/assets/javascripts/app/controllers/task_widget.js.coffee b/app/assets/javascripts/app/controllers/task_widget.js.coffee index 3e345d870..6b235579f 100644 --- a/app/assets/javascripts/app/controllers/task_widget.js.coffee +++ b/app/assets/javascripts/app/controllers/task_widget.js.coffee @@ -10,37 +10,31 @@ class App.TaskWidget extends App.Controller App.Event.bind 'ui:rerender', (data) => @render() - # rebuild chat widget + # rebuild taskbar widget App.Event.bind 'auth', (user) => App.TaskManager.reset() @el.html('') - sync = => - App.TaskManager.sync() - @delay( sync, 3000, 'task-widget' ) - - @delay( sync, 5000, 'task-widget' ) - render: -> return if _.isEmpty( @Session.all() ) tasks = App.TaskManager.all() item_list = [] - for key, task of tasks + for task in tasks data = url: '#' id: false title: App.i18n.translateInline('Loading...') head: App.i18n.translateInline('Loading...') - if task.worker - meta = task.worker.meta() + worker = App.TaskManager.worker( task.type, task.type_id ) + if worker + meta = worker.meta() if meta data = meta data.title = App.i18n.escape( data.title ) data.head = App.i18n.escape( data.head ) item = {} - item.key = key item.task = task item.data = data item_list.push item @@ -52,34 +46,41 @@ class App.TaskWidget extends App.Controller remove: (e) => e.preventDefault() - key = $(e.target).parent().data('id') + type_id = $(e.target).parent().data('type-id') + type = $(e.target).parent().data('type') + if !type_id && !type + throw "No such type and type-id attributes found for task item" # check if input has changed - task = App.TaskManager.get( key ) - if task.worker && task.worker.changed - if task.worker.changed() + worker = App.TaskManager.worker( type, type_id ) + if worker && worker.changed + if worker.changed() return if !window.confirm( App.i18n.translateInline('Tab has changed, you really want to close it?') ) # check if active task is closed - task_last = undefined - tasks_all = App.TaskManager.all() + currentTask = App.TaskManager.get( type, type_id ) + tasks = App.TaskManager.all() active_is_closed = false - for task_key, task of tasks_all - if task.active && task_key.toString() is key.toString() + for task in tasks + if currentTask.active && task.type is type && task.type_id.toString() is type_id.toString() active_is_closed = true # remove task - App.TaskManager.remove( key ) + App.TaskManager.remove( type, type_id ) @render() # navigate to next task if needed - if active_is_closed && !_.isEmpty( tasks_all ) - for key, task of tasks_all + tasks = App.TaskManager.all() + if active_is_closed && !_.isEmpty( tasks ) + task_last = undefined + for task in tasks task_last = task if task_last - @navigate task_last.worker.url() + worker = App.TaskManager.worker( task_last.type, task_last.type_id ) + if worker + @navigate worker.url() return - if _.isEmpty( tasks_all ) + if _.isEmpty( tasks ) @navigate '#' _getTaskActions: -> diff --git a/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee b/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee index d43aca9c8..3b21e534b 100644 --- a/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/task_manager.js.coffee @@ -14,240 +14,216 @@ class App.TaskManager _instance ?= new _Singleton _instance.add( type, type_id, callback, params, to_not_show, state ) - @get: ( key ) -> + @get: ( type, type_id ) -> if _instance == undefined _instance ?= new _Singleton - _instance.get( key ) + _instance.get( type, type_id ) - @update: ( key, params ) -> + @update: ( type, type_id, params ) -> if _instance == undefined _instance ?= new _Singleton - _instance.update( key, params ) + _instance.update( type, type_id, params ) - @remove: ( key ) -> + @remove: ( type, type_id ) -> if _instance == undefined _instance ?= new _Singleton - _instance.remove( key ) + _instance.remove( type, type_id ) - @notify: ( key ) -> + @notify: ( type, type_id ) -> if _instance == undefined _instance ?= new _Singleton - _instance.notify( key ) + _instance.notify( type, type_id ) @reset: -> if _instance == undefined _instance ?= new _Singleton _instance.reset() - @syncInitial: -> + @worker: ( type, type_id ) -> if _instance == undefined _instance ?= new _Singleton - _instance.syncTasksInitial() + _instance.worker( type, type_id ) - @syncSave: -> + @workerAll: -> if _instance == undefined _instance ?= new _Singleton - _instance.syncSave() - - @sync: -> - if _instance == undefined - _instance ?= new _Singleton - _instance.syncTasks() + _instance.workerAll() class _Singleton extends App.Controller @include App.Log constructor: -> - @tasks = {} - @task_count = 0 - @syncTasksInitial() - + @workers = {} + @workersStarted = {} + @activeTask = undefined + @tasksInitial() + all: -> - @tasks + App.Taskbar.all() + + worker: ( type, type_id ) -> + key = @keyGenerate(type, type_id) + return @workers[ key ] if @workers[ key ] + return + + workerAll: -> + @workers add: ( type, type_id, callback, params, to_not_show = false, state ) -> - for key, task of @tasks - if task.type is type && task.type_id is type_id - return key if to_not_show - $('#content').empty() - $('.content_permanent').hide() - $('.content_permanent').removeClass('active') - $('#content_permanent_' + key ).show() - $('#content_permanent_' + key ).addClass('active') - @tasks[key].worker.activate() - @tasks[key].notify = false - for task_key, task of @tasks - if task_key isnt key - task.active = false - else - task.active = true - App.Event.trigger 'ui:rerender' - App.Event.trigger 'ui:rerender:content' - return key - - @task_count++ - if !to_not_show - for task_key, task of @tasks - task.active = false active = true if to_not_show active = false + + # create new task if not exists + task = @get( type, type_id ) +# console.log('add', type, type_id, callback, params, to_not_show, state, task) + if !task + task = new App.Taskbar + task.load( + type: type + type_id: type_id + params: params + callback: callback + notify: false + active: active + ) + task.save() + + tasks = @all() + + # empty static content if task is shown + key = @keyGenerate(type, type_id) if active + @activeTask = key $('#content').empty() - $('#content_permanent').append('
') - - if active + # hide all tasks $('.content_permanent').hide() $('.content_permanent').removeClass('active') - $('#content_permanent_' + @task_count ).show() - $('#content_permanent_' + @task_count ).addClass('active') + + # create div for task if not exists + if !$("#content_permanent_#{key}")[0] + $('#content_permanent').append('
') + + # set task to shown and active + if @activeTask is key + $('#content_permanent_' + key ).show() + $('#content_permanent_' + key ).addClass('active') else - $('#content_permanent_' + @task_count ).removeClass('active') - $('#content_permanent_' + @task_count ).hide() + $('#content_permanent_' + key ).hide() + $('#content_permanent_' + key ).removeClass('active') + + # set all tasks to active false, only new/selected one to active + if active + for task in tasks + task_key = @keyGenerate(task.type, task.type_id) + if task_key isnt key + task.active = false + else + task.active = true + task.save() + else + for task in tasks + task_key = @keyGenerate(task.type, task.type_id) + if @activeTask isnt task_key + if task.active + task.active = false + task.save() + + # start worker for task if not exists + @startController(type, type_id, callback, params, state, key, to_not_show) + + App.Event.trigger 'ui:rerender' + App.Event.trigger 'ui:rerender:content' + return key + + startController: (type, type_id, callback, params, state, key, to_not_show) => + +# console.log('controller started...', callback, type, type_id, params, state) + + # activate controller + worker = @worker( type, type_id ) + if worker && worker.activate + worker.activate() + + # return if controller is alreary started + return if @workersStarted[key] + @workersStarted[key] = true # create new controller instanz params_app = _.clone(params) - params_app['el'] = $('#content_permanent_' + @task_count ) - params_app['task_key'] = @task_count + params_app['el'] = $('#content_permanent_' + key ) + params_app['task_key'] = key # check if we have old state there if !state - oldTask = @get_by_type( type, type_id ) + oldTask = @get( type, type_id ) if oldTask state = oldTask.state params_app['form_state'] = state + if to_not_show params_app['doNotLog'] = 1 a = new App[callback]( params_app ) - - # remember new controller / prepare for task storage - task = - type: type - type_id: type_id - params: params - callback: callback - worker: a - active: active - @tasks[@task_count] = task + @workers[ key ] = a # activate controller if !to_not_show a.activate() - App.Event.trigger 'ui:rerender' + return a - # add new controller to task storage - if !to_not_show - @syncAdd(task) + get: ( type, type_id ) => + tasks = App.Taskbar.all() + for task in tasks + return task if task.type is type && task.type_id.toString() is type_id.toString() + return +# throw "No such task with '#{type}' and '#{type_id}'" - @task_count - - get: ( key ) => - return @tasks[key] - - update: ( key, params ) => - return false if !@tasks[key] + update: ( type, type_id, params ) => + task = @get( type, type_id ) + if !task + throw "No such task with '#{type}' and '#{type_id}' to update" for item, value of params - @tasks[key][item] = value - @syncSave() + task.updateAttribute(item, value) +# task.save() - remove: ( key, to_not_show = false ) => - if @tasks[key] - @tasks[key].worker.release() - if !to_not_show - @syncRemove( @tasks[key] ) - delete @tasks[key] + remove: ( type, type_id, to_not_show = false ) => + task = @get( type, type_id ) + if !task + throw "No such task with '#{type}' and '#{type_id}' to remove" + + worker = @worker( type, type_id ) + if worker && worker.release + worker.release() + @workersStarted[ @keyGenerate(type, type_id) ] = false + task.destroy() App.Event.trigger 'ui:rerender' - notify: ( key ) => - @tasks[key].notify = true + notify: ( type, type_id ) => + task = @get( type, type_id ) + if !task + throw "No such task with '#{type}' and '#{type_id}' to notify" + task.notify = true reset: => - @tasks = {} + App.Taskbar.deleteAll() App.Event.trigger 'ui:rerender' - get_by_type: (type, type_id) => - store = @syncLoad() || [] - for item in store - return item if item.type is type && item.type_id is type_id - - syncAdd: (task) => - store = @syncLoad() || [] - for item in store - return if item.type is task.type && item.type_id is task.type_id - item = - type: task.type - type_id: task.type_id - params: task.params - callback: task.callback - store.push item - App.Store.write( 'tasks', store ) - - syncRemove: (task) => - store = @syncLoad() || [] - storeNew = [] - for item in store - if item.type isnt task.type || item.type_id isnt task.type_id - storeNew.push item - App.Store.write( 'tasks', storeNew ) - - syncSave: => - store = @syncLoad() || [] - storeNew = [] - for item in store - for key, task of @tasks - if task.type is item.type && task.type_id is item.type_id - console.log('MATCH', item) - if @tasks[key]['state'] - item['state'] = @tasks[key]['state'] - storeNew.push item -# if @tasks[key] -# @tasks[key].worker.release() -# -# storeNew.push item -# item = -# type: task.type -# type_id: task.type_id -# params: task.params -# callback: task.callback - App.Store.write( 'tasks', storeNew ) - - syncLoad: => - App.Store.get( 'tasks' ) - - syncTasksInitial: => + tasksInitial: => # reopen tasks - store = _.clone(@syncLoad()) - return if !store + App.Taskbar.fetch() + tasks = @all() + return if !tasks task_count = 0 - for task in store + for task in tasks task_count += 1 @delay( => - task = store.shift() + task = tasks.shift() @add(task.type, task.type_id, task.callback, task.params, true, task.state) task_count * 500 ) - syncTasks: => - store = @syncLoad() || [] - - # open tasks - for item in store - existsLocal = false - for task_key, task of @tasks - if item.type is task.type && item.type_id is task.type_id - # also open here - existsLocal = true - if !existsLocal - @add(item.type, item.type_id, item.callback, item.params, true) - - # close tasks - for task_key, task of @tasks - onlyLocal = true - for item in store - if item.type is task.type && item.type_id is task.type_id - onlyLocal = false - if onlyLocal - @remove( task_key, true ) + keyGenerate: ( type, type_id )-> + "#{type}_#{type_id}" diff --git a/app/assets/javascripts/app/views/task_widget.jst.eco b/app/assets/javascripts/app/views/task_widget.jst.eco index d01b2af76..fb1cedc9b 100644 --- a/app/assets/javascripts/app/views/task_widget.jst.eco +++ b/app/assets/javascripts/app/views/task_widget.jst.eco @@ -1,6 +1,6 @@ -
+
<% for item in @item_list: %> - <%- item.data.head %> + <%- item.data.head %> <% end %> <% if !_.isEmpty( @taskBarActions ): %>
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 96e379f6c..c48fe7f4f 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,6 +12,7 @@ //not_used= require_tree ./app/lib/spine //= require ./app/lib/spine/spine.js //= require ./app/lib/spine/ajax.js +//= require ./app/lib/spine/local.js //= require ./app/lib/spine/route.js //= require ./app/lib/flot/jquery.flot.js From 903b5a158de5e8f5e764c1b93d34538983e42f9a Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 31 May 2013 13:34:11 +0200 Subject: [PATCH 12/12] Improved tests. --- test/browser/agent_ticket_actions_simple_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/browser/agent_ticket_actions_simple_test.rb b/test/browser/agent_ticket_actions_simple_test.rb index af069c12d..5ce519851 100644 --- a/test/browser/agent_ticket_actions_simple_test.rb +++ b/test/browser/agent_ticket_actions_simple_test.rb @@ -99,6 +99,11 @@ class AgentTicketActionSimpleTest < TestCase }, # update ticket + { + :execute => 'select', + :css => 'select[name="ticket_article_type_id"]', + :value => 'note', + }, { :execute => 'check', :css => 'textarea[name="body"]', @@ -216,6 +221,11 @@ class AgentTicketActionSimpleTest < TestCase }, # update ticket + { + :execute => 'select', + :css => 'select[name="ticket_article_type_id"]', + :value => 'note', + }, { :execute => 'check', :css => '.content_permanent.active textarea[name="body"]',