netzke-communitypack 0.1.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +2 -5
  3. data/Rakefile +36 -1
  4. data/lib/netzke-communitypack.rb +9 -4
  5. data/lib/netzke-communitypack/version.rb +2 -9
  6. data/lib/netzke/communitypack/google_map_panel.rb +22 -22
  7. data/lib/netzke/communitypack/google_map_panel/javascript/GMapPanel.js +2 -1
  8. data/lib/netzke/communitypack/live_search_grid_panel.rb +39 -36
  9. data/lib/netzke/communitypack/model_explorer.rb +68 -0
  10. data/lib/netzke/communitypack/one_to_many_explorer.rb +78 -0
  11. data/lib/netzke/communitypack/one_to_many_explorer/javascripts/one_to_many_explorer.js +14 -0
  12. data/lib/netzke/communitypack/workspace.rb +94 -0
  13. data/lib/netzke/communitypack/workspace/javascripts/workspace.js +73 -0
  14. data/lib/tasks/netzke-communitypack_tasks.rake +4 -0
  15. data/test/{rails_app → dummy}/Rakefile +2 -2
  16. data/test/dummy/app/assets/javascripts/application.js +9 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  18. data/test/{rails_app → dummy}/app/controllers/application_controller.rb +0 -0
  19. data/test/{rails_app → dummy}/app/helpers/application_helper.rb +0 -0
  20. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/test/{rails_app → dummy}/config.ru +1 -1
  22. data/test/{rails_app → dummy}/config/application.rb +12 -12
  23. data/test/dummy/config/boot.rb +10 -0
  24. data/test/{rails_app → dummy}/config/database.yml +4 -1
  25. data/test/{rails_app → dummy}/config/environment.rb +1 -1
  26. data/test/{rails_app → dummy}/config/environments/development.rb +9 -5
  27. data/test/{rails_app → dummy}/config/environments/production.rb +25 -14
  28. data/test/{rails_app → dummy}/config/environments/test.rb +7 -3
  29. data/test/{rails_app → dummy}/config/initializers/backtrace_silencers.rb +0 -0
  30. data/test/{rails_app → dummy}/config/initializers/inflections.rb +0 -0
  31. data/test/{rails_app → dummy}/config/initializers/mime_types.rb +0 -0
  32. data/test/{rails_app → dummy}/config/initializers/secret_token.rb +1 -1
  33. data/test/dummy/config/initializers/session_store.rb +8 -0
  34. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  35. data/test/{rails_app → dummy}/config/locales/en.yml +1 -1
  36. data/test/{rails_app → dummy}/config/routes.rb +2 -5
  37. data/test/{rails_app → dummy}/public/404.html +0 -0
  38. data/test/{rails_app → dummy}/public/422.html +0 -0
  39. data/test/{rails_app → dummy}/public/500.html +0 -0
  40. data/test/{rails_app → dummy}/public/favicon.ico +0 -0
  41. data/test/{rails_app → dummy}/script/rails +0 -0
  42. data/test/netzke-communitypack_test.rb +7 -0
  43. data/test/test_helper.rb +10 -0
  44. metadata +134 -134
  45. data/.gitignore +0 -4
  46. data/CHANGELOG.rdoc +0 -3
  47. data/Gemfile +0 -4
  48. data/lib/netzke/communitypack/viewport.rb +0 -4
  49. data/netzke-communitypack.gemspec +0 -26
  50. data/test/rails_app/.gitignore +0 -4
  51. data/test/rails_app/Gemfile +0 -34
  52. data/test/rails_app/Gemfile.lock +0 -89
  53. data/test/rails_app/README +0 -256
  54. data/test/rails_app/app/components/test_panel.rb +0 -3
  55. data/test/rails_app/app/controllers/components_controller.rb +0 -5
  56. data/test/rails_app/app/helpers/components_helper.rb +0 -2
  57. data/test/rails_app/app/views/components/show.html.erb +0 -13
  58. data/test/rails_app/app/views/layouts/application.html.erb +0 -17
  59. data/test/rails_app/config/boot.rb +0 -13
  60. data/test/rails_app/config/initializers/session_store.rb +0 -8
  61. data/test/rails_app/db/seeds.rb +0 -7
  62. data/test/rails_app/doc/README_FOR_APP +0 -2
  63. data/test/rails_app/lib/tasks/.gitkeep +0 -0
  64. data/test/rails_app/public/images/rails.png +0 -0
  65. data/test/rails_app/public/index.html +0 -239
  66. data/test/rails_app/public/javascripts/application.js +0 -2
  67. data/test/rails_app/public/javascripts/controls.js +0 -965
  68. data/test/rails_app/public/javascripts/dragdrop.js +0 -974
  69. data/test/rails_app/public/javascripts/effects.js +0 -1123
  70. data/test/rails_app/public/javascripts/prototype.js +0 -6001
  71. data/test/rails_app/public/javascripts/rails.js +0 -175
  72. data/test/rails_app/public/robots.txt +0 -5
  73. data/test/rails_app/public/stylesheets/.gitkeep +0 -0
  74. data/test/rails_app/test/functional/components_controller_test.rb +0 -8
  75. data/test/rails_app/test/performance/browsing_test.rb +0 -9
  76. data/test/rails_app/test/test_helper.rb +0 -13
  77. data/test/rails_app/test/unit/helpers/components_helper_test.rb +0 -4
  78. data/test/rails_app/vendor/plugins/.gitkeep +0 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,6 +1,3 @@
1
- = Netzke components submitted by the community
1
+ = Netzke Communitypack
2
2
 
3
- Includes the following components:
4
-
5
- * LiveSearchGridPanel - GridPanel extension with a configurable live-search textfield
6
- * GoogleMapPanel - Panel showing a Google map
3
+ A bunch of community-written Netzke components.
data/Rakefile CHANGED
@@ -1,2 +1,37 @@
1
- require 'bundler'
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'NetzkeCommunitypack'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
2
25
  Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task :default => :test
@@ -1,5 +1,10 @@
1
- module Netzke
2
- module Communitypack
3
- # Your code goes here...
4
- end
1
+ # External dependencies
2
+ require 'netzke-core'
3
+ require 'active_support/dependencies'
4
+
5
+ # Make components auto-loadable
6
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
7
+
8
+ module NetzkeCommunitypack
9
+
5
10
  end
@@ -1,10 +1,3 @@
1
- module Netzke
2
- module Communitypack
3
- MAJOR = 0
4
- MINOR = 1
5
- PATCH = 3
6
-
7
- STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
- VERSION = STRING
9
- end
1
+ module NetzkeCommunitypack
2
+ VERSION = "0.7.0" # Keep the major and minor versions synced with those of Netzke Core gem.
10
3
  end
@@ -1,30 +1,30 @@
1
1
  # Creates a google map panel. The panel additionaly has the options of google maps, i.e.:
2
2
  # * +zoom_level+ - The initial zoom level
3
- # * +gmap_type+ -
4
- # * +map_conf_opts+ -
3
+ # * +gmap_type+ -
4
+ # * +map_conf_opts+ -
5
5
  # * +map_controlls+ -
6
6
  # * +set_center+ - The initial map position
7
7
  # * +markers+ - Initial markers on the page
8
8
  # ...
9
9
  class Netzke::Communitypack::GoogleMapPanel < ::Netzke::Base
10
- js_include "#{File.dirname(__FILE__)}/google_map_panel/javascript/GMapPanel.js"
11
-
12
- # default configuration
13
- config do
14
- {
15
- :zoom_level => 14,
16
- :gmap_type => 'map',
17
- :layout => :fit,
18
- :map_conf_opts => ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'],
19
- :map_controls => ['GSmallMapControl','GMapTypeControl','NonExistantControl'],
20
- :set_center => {
21
- :geo_code_addr => 'Flottwellstr. 4-5, 10785 Berlin, Germany',
22
- :marker => {
23
- :title => 'pme Familienservice GmbH'
24
- }
25
- }
26
- }
27
- end
28
-
29
- js_base_class 'Ext.ux.GMapPanel'
10
+ js_base_class 'Ext.ux.GMapPanel'
11
+
12
+ js_include "#{File.dirname(__FILE__)}/google_map_panel/javascript/GMapPanel.js"
13
+
14
+ # default configuration
15
+ def configuration
16
+ super.merge({
17
+ :zoom_level => 14,
18
+ :gmap_type => 'map',
19
+ :layout => :fit,
20
+ :map_conf_opts => ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'],
21
+ :map_controls => ['GSmallMapControl','GMapTypeControl','NonExistantControl'],
22
+ :set_center => {
23
+ :geo_code_addr => 'Flottwellstr. 4-5, 10785 Berlin, Germany',
24
+ :marker => {
25
+ :title => 'pme Familienservice GmbH'
26
+ }
27
+ }
28
+ })
29
+ end
30
30
  end
@@ -72,6 +72,7 @@ Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {
72
72
  this.addMarkers(this.markers);
73
73
  this.addMapControls();
74
74
  this.addOptions();
75
+ this.gmap.checkResize();
75
76
  },
76
77
  onResize : function(w, h){
77
78
 
@@ -212,4 +213,4 @@ Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {
212
213
 
213
214
  });
214
215
 
215
- Ext.reg('gmappanel', Ext.ux.GMapPanel);
216
+ Ext.reg('gmappanel', Ext.ux.GMapPanel);
@@ -7,40 +7,43 @@
7
7
  # * +live_search_scope+ - The scope name for filtering the results by the live search (default: :live_search)
8
8
  #
9
9
  class Netzke::Communitypack::LiveSearchGridPanel < ::Netzke::Basepack::GridPanel
10
- config :tbar => ['->', {
11
- :xtype => 'textfield',
12
- :id => 'live_search_field',
13
- :enable_key_events => true,
14
- :ref => '../live_search_field',
15
- :empty_text => 'Search'
16
- }]
17
-
18
- js_method :init_component, <<-JS
19
- function() {
20
- #{js_full_class_name}.superclass.initComponent.call(this);
21
-
22
- this.liveSearchBuffer = '';
23
- this.live_search_field.on('keydown', function() { this.onLiveSearch(); }, this, { buffer: 500 });
24
- this.live_search_field.on('blur', function() { this.onLiveSearch(); }, this, { buffer: 500 });
25
- }
26
- JS
27
-
28
- js_method :on_live_search, <<-JS
29
- function() {
30
- var search_text = this.getTopToolbar().get('live_search_field').getValue();
31
- if (search_text == this.liveSearchBuffer) return;
32
- this.liveSearchBuffer = search_text;
33
- this.getStore().setBaseParam('live_search', search_text);
34
- this.getStore().load();
35
- }
36
- JS
37
-
38
- def get_data(*args)
39
- params = args.first
40
- search_scope = config[:live_search_scope] || :live_search
41
- data_class.send(search_scope, params && params[:live_search] || '').scoping do
42
- super
43
- end
44
- end
45
-
10
+ def configuration
11
+ super.merge({
12
+ :tbar => ['->', {
13
+ :xtype => 'textfield',
14
+ :enable_key_events => true,
15
+ :ref => '../live_search_field',
16
+ :empty_text => 'Search'
17
+ }]
18
+ })
19
+ end
20
+
21
+ js_method :init_component, <<-JS
22
+ function() {
23
+ #{js_full_class_name}.superclass.initComponent.call(this);
24
+
25
+ this.liveSearchBuffer = '';
26
+ this.live_search_field.on('keydown', function() { this.onLiveSearch(); }, this, { buffer: 500 });
27
+ this.live_search_field.on('blur', function() { this.onLiveSearch(); }, this, { buffer: 500 });
28
+ }
29
+ JS
30
+
31
+ js_method :on_live_search, <<-JS
32
+ function() {
33
+ var search_text = this.live_search_field.getValue();
34
+ if (search_text == this.liveSearchBuffer) return;
35
+ this.liveSearchBuffer = search_text;
36
+ this.getStore().setBaseParam('live_search', search_text);
37
+ this.getStore().load();
38
+ }
39
+ JS
40
+
41
+ def get_data(*args)
42
+ params = args.first
43
+ search_scope = config[:live_search_scope] || :live_search
44
+ data_class.send(search_scope, params && params[:live_search] || '').scoping do
45
+ super
46
+ end
47
+ end
48
+
46
49
  end
@@ -0,0 +1,68 @@
1
+ module Netzke
2
+ module Communitypack
3
+ # 2 regions - "grid" and "form", form displaying the details of the record selected in the grid.
4
+ #
5
+ # Accepts the following config options:
6
+ # * :model - name of the model, e.g. "User"
7
+ # * :grid_config (optional) - a config hash passed to the grid
8
+ # * :form_config (optional) - a config hash passed to the form
9
+ class ModelExplorer < Netzke::Basepack::BorderLayoutPanel
10
+
11
+ delegates_to_dsl :model, :grid_config, :form_config
12
+
13
+ js_properties(
14
+ :prevent_header => true,
15
+ :border => true
16
+ )
17
+
18
+ def configuration
19
+ super.tap do |c|
20
+
21
+ # merge default container and collection config with the one provided by the user
22
+ c[:grid_config] = {
23
+ :region => :west,
24
+ :class_name => "Netzke::Basepack::GridPanel",
25
+ :model => c[:model],
26
+ :item_id => 'grid'
27
+ }.merge(c[:grid_config] || {})
28
+
29
+ c[:form_config] = {
30
+ :class_name => "Netzke::Basepack::FormPanel",
31
+ :model => c[:model],
32
+ :region => :center,
33
+ :item_id => 'form'
34
+ }.merge(c[:form_config] || {})
35
+
36
+ # set default width/height for regions
37
+ c[:grid_config][:width] ||= 300 if [:west, :east].include?(c[:grid_config][:region].to_sym)
38
+ c[:grid_config][:height] ||= 150 if [:north, :south].include?(c[:grid_config][:region].to_sym)
39
+
40
+ c[:items] = [c[:grid_config], c[:form_config]]
41
+ end
42
+ end
43
+
44
+ endpoint :select_record do |params|
45
+ # store selected container record id in the session for this component's instance
46
+ component_session[:selected_record_id] = params[:id]
47
+
48
+ # {:form => {:set_title => "Blah"}}
49
+ end
50
+
51
+ js_method :init_component, <<-JS
52
+ function(){
53
+ // calling superclass's initComponent
54
+ this.callParent();
55
+
56
+ this.grid = this.getComponent('grid');
57
+ this.form = this.getComponent('form');
58
+
59
+ // setting the 'rowclick' event
60
+ this.grid.getView().on('itemclick', function(view, record){
61
+ this.selectRecord({id: record.getId()});
62
+ this.form.netzkeLoad({id: record.getId()});
63
+ }, this);
64
+ }
65
+ JS
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,78 @@
1
+ module Netzke
2
+ module Communitypack
3
+ # 2 grids - "container" and "collection" - bound with a one-to-many relationship.
4
+ # The collection model should implement belongs_to in respect to the container model.
5
+ #
6
+ # Accepts the following config options:
7
+ # * :container_model - name of the container model, e.g. "User"
8
+ # * :collection_model - name of the collection model, e.g. "Issue" (belongs_to :user)
9
+ # * :container_config (optional) - a config hash passed to the container grid
10
+ # * :collection_config (optional) - a config hash passod to the collection grid
11
+ # * :association (optional) - the name of the association used in belongs_to macro. Defaults to the underscored name of the container model.
12
+ class OneToManyExplorer < Netzke::Basepack::BorderLayoutPanel
13
+ js_mixin
14
+
15
+ delegates_to_dsl :container_model, :collection_model, :container_config, :collection_config, :association
16
+
17
+ js_properties(
18
+ :prevent_header => true,
19
+ :border => true
20
+ )
21
+
22
+ def configuration
23
+ super.tap do |c|
24
+
25
+ # merge default container and collection config with the one provided by the user
26
+ c[:container_config] = {
27
+ :region => :west,
28
+ :class_name => "Netzke::Basepack::GridPanel"
29
+ }.merge(c[:container_config] || {})
30
+
31
+ c[:collection_config] = {
32
+ :class_name => "Netzke::Basepack::GridPanel"
33
+ }.merge(c[:collection_config] || {})
34
+
35
+ # set default width/height for regions
36
+ c[:container_config][:width] ||= 300 if [:west, :east].include?(c[:container_config][:region].to_sym)
37
+ c[:container_config][:height] ||= 200 if [:north, :south].include?(c[:container_config][:region].to_sym)
38
+
39
+ # figure out collection_class from config or from the passed component
40
+ container_class = c[:container_config][:model].try(:constantize) || c[:container_config][:class_name].constantize.new.data_class
41
+ collection_class = c[:collection_config][:model].try(:constantize) || c[:collection_config][:class_name].constantize.new.data_class
42
+
43
+ # use the shortcuts for models
44
+ c[:container_config][:model] ||= c[:container_model] || container_class.name
45
+ c[:collection_config][:model] ||= c[:collection_model] || collection_class.name
46
+
47
+ # we need to get the association reflection in order to properly set the collection grid scope
48
+ c[:association] ||= c[:container_config][:model].underscore.to_sym # the belongs_to association, e.g. "user"
49
+
50
+ association = collection_class.reflect_on_association(c[:association])
51
+
52
+ # if we have extra scopes received in the config, take them into account!
53
+ passed_scope = c[:collection_config][:scope] || {}
54
+ passed_strong_default_attrs = c[:collection_config][:strong_default_attrs] || {}
55
+
56
+ c[:items] = [
57
+ c[:container_config].merge(:item_id => 'container'),
58
+
59
+
60
+ c[:collection_config].merge(
61
+ :region => :center,
62
+ :item_id => 'collection',
63
+ :scope => {association.foreign_key.to_sym => component_session[:selected_container_record_id]}.merge(passed_scope),
64
+ :strong_default_attrs => {association.foreign_key.to_sym => component_session[:selected_container_record_id]}.merge(passed_strong_default_attrs),
65
+ :load_inline_data => false
66
+ )
67
+ ]
68
+ end
69
+ end
70
+
71
+ endpoint :select_container_record do |params|
72
+ # store selected container record id in the session for this component's instance
73
+ component_session[:selected_container_record_id] = params[:id]
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,14 @@
1
+ {
2
+ initComponent: function() {
3
+ // calling superclass's initComponent
4
+ this.callParent();
5
+
6
+ // setting the 'rowclick' event
7
+ var view = this.getComponent('container').getView();
8
+ view.on('itemclick', function(view, record){
9
+ // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
10
+ this.selectContainerRecord({id: record.get('id')});
11
+ this.getComponent('collection').getStore().load();
12
+ }, this);
13
+ }
14
+ }
@@ -0,0 +1,94 @@
1
+ module Netzke
2
+ module Communitypack
3
+ # A component that allows for dynamical loading/unloading of other Netzke components in tabs.
4
+ # It can be manipulated by calling the <js>loadChild</js> method, e.g.:
5
+ #
6
+ # workspace.loadChild("UserGrid", {newTab: true})
7
+ #
8
+ # - will load a UserGrid component from the server in a new tab.
9
+ class Workspace < Netzke::Base
10
+ js_base_class "Ext.tab.Panel"
11
+ js_property :prevent_header, true
12
+
13
+ js_mixin
14
+
15
+ action :remove_all
16
+
17
+ def default_config
18
+ super.tap do |c|
19
+ c[:items] = ([dashboard_config] + stored_tabs).each_with_index.map do |tab,i|
20
+ {
21
+ :layout => 'fit',
22
+ :title => tab[:title],
23
+ :closable => i > 0, # all closable except first
24
+ :netzke_component_id => tab[:name],
25
+ :items => !components[tab[:name].to_sym][:lazy_loading] && [tab[:name].to_sym.component]
26
+ }
27
+ end
28
+ end
29
+ end
30
+
31
+ def dashboard_config
32
+ {
33
+ :title => "Dashboard",
34
+ :class_name => "Netzke::Basepack::Panel"
35
+ }.merge!(@passed_config[:dashboard_config] || {}).merge(:name => 'cmp0', :lazy_loading => false)
36
+ end
37
+
38
+ # Overriding this to allow for dynamically declared components
39
+ def components
40
+ stored_tabs.inject({}){ |r,tab| r.merge(tab[:name].to_sym => tab.reverse_merge(:prevent_header => true, :lazy_loading => true, :border => false)) }.merge(:cmp0 => dashboard_config)
41
+ end
42
+
43
+ # Overriding the deliver_component endpoint, to dynamically add tabs and replace components in existing tabs
44
+ def deliver_component_endpoint(params)
45
+ cmp_name = params[:name]
46
+ cmp_index = cmp_name.sub("cmp", "").to_i
47
+
48
+ if params[:component].present?
49
+ current_tabs = stored_tabs
50
+
51
+ # we need to instantiate the newly added child to get access to its title
52
+ cmp_class = constantize_class_name(params[:component])
53
+ raise RuntimeError, "Could not find class #{params[:component]}" if cmp_class.nil?
54
+
55
+ cmp_config = {:name => params[:name], :class_name => cmp_class.name}.merge(params[:config] || {}).symbolize_keys
56
+ cmp_instance = cmp_class.new(cmp_config, self)
57
+ new_tab_short_config = cmp_config.merge(:title => cmp_instance.js_config[:title] || cmp_instance.class.js_properties[:title]) # here we set the title
58
+
59
+ if stored_tabs.empty? || cmp_index > stored_tabs.last[:name].sub("cmp", "").to_i
60
+ # add new tab to persistent storage
61
+ current_tabs << new_tab_short_config
62
+ else
63
+ # replace existing tab in the storage
64
+ current_tabs[current_tabs.index(current_tabs.detect{ |tab| tab[:name] == cmp_name })] = new_tab_short_config
65
+ end
66
+
67
+ component_session[:items] = current_tabs
68
+ @stored_tabs = nil # reset cache
69
+ end
70
+
71
+ super(params)
72
+ end
73
+
74
+ # Clean the session on request. More clean-up may be needed later, as we start using persistent configuration.
75
+ endpoint :server_remove_all do |params|
76
+ component_session[:items] = []
77
+ end
78
+
79
+ # Removes a closed tab's component from the storage.
80
+ endpoint :server_remove_tab do |params|
81
+ component_session[:items].delete_if{ |item| item[:name] == params[:name] }
82
+ {}
83
+ end
84
+
85
+ private
86
+
87
+ # We store these in component_session atm. May as well be in persistent storage, depending on the requirements
88
+ def stored_tabs
89
+ @stored_tabs ||= component_session[:items] || []
90
+ end
91
+
92
+ end
93
+ end
94
+ end