skozlov-netzke-basepack 0.1.1.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +2 -19
  4. data/README.rdoc +87 -0
  5. data/Rakefile +28 -12
  6. data/TODO.rdoc +7 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +3 -0
  9. data/init.rb +0 -1
  10. data/javascripts/basepack.js +839 -49
  11. data/lib/app/models/netzke_auto_column.rb +56 -0
  12. data/lib/netzke/accordion_panel.rb +113 -0
  13. data/lib/netzke/active_record/basepack.rb +104 -0
  14. data/lib/netzke/active_record/data_accessor.rb +21 -0
  15. data/lib/netzke/basic_app.rb +325 -0
  16. data/lib/netzke/border_layout_panel.rb +128 -0
  17. data/lib/netzke/configuration_panel.rb +24 -0
  18. data/lib/netzke/data_accessor.rb +71 -0
  19. data/lib/netzke/ext.rb +6 -0
  20. data/lib/netzke/field_model.rb +131 -0
  21. data/lib/netzke/fields_configurator.rb +95 -0
  22. data/lib/netzke/form_panel.rb +214 -0
  23. data/lib/netzke/form_panel_api.rb +74 -0
  24. data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
  25. data/lib/netzke/form_panel_js.rb +129 -0
  26. data/lib/netzke/grid_panel.rb +442 -0
  27. data/lib/netzke/grid_panel_api.rb +352 -0
  28. data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
  29. data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
  30. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  31. data/lib/netzke/grid_panel_js.rb +721 -0
  32. data/lib/netzke/panel.rb +13 -0
  33. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  34. data/lib/netzke/property_editor.rb +105 -0
  35. data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
  36. data/lib/netzke/search_panel.rb +62 -0
  37. data/lib/netzke/tab_panel.rb +160 -0
  38. data/lib/netzke/table_editor.rb +118 -0
  39. data/lib/netzke/tree_panel.rb +73 -0
  40. data/lib/netzke/wrapper.rb +42 -0
  41. data/lib/netzke-basepack.rb +9 -15
  42. data/netzke-basepack.gemspec +147 -20
  43. data/stylesheets/basepack.css +26 -0
  44. data/test/app_root/app/models/book.rb +1 -1
  45. data/test/app_root/config/environment.rb +1 -0
  46. data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
  47. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  48. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
  49. data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
  50. data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
  51. data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  52. data/test/test_helper.rb +1 -2
  53. data/test/unit/accordion_panel_test.rb +20 -0
  54. data/test/unit/active_record_basepack_test.rb +54 -0
  55. data/test/unit/grid_panel_test.rb +43 -0
  56. data/test/unit/helper_model_test.rb +30 -0
  57. data/test/unit/netzke_basepack_test.rb +4 -0
  58. data/test/unit/tab_panel_test.rb +21 -0
  59. metadata +96 -72
  60. data/CHANGELOG +0 -14
  61. data/Manifest +0 -65
  62. data/README.mdown +0 -18
  63. data/generators/netzke_basepack/USAGE +0 -8
  64. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
  65. data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
  66. data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
  67. data/lib/app/models/netzke_grid_column.rb +0 -23
  68. data/lib/netzke/accordion.rb +0 -11
  69. data/lib/netzke/ar_ext.rb +0 -163
  70. data/lib/netzke/column.rb +0 -43
  71. data/lib/netzke/container.rb +0 -81
  72. data/lib/netzke/grid.rb +0 -120
  73. data/lib/netzke/grid_interface.rb +0 -156
  74. data/lib/netzke/grid_js_builder.rb +0 -276
  75. data/lib/netzke/preference_grid.rb +0 -43
  76. data/lib/netzke/properties_tool.rb +0 -66
  77. data/lib/netzke/property_grid.rb +0 -60
  78. data/test/ar_ext_test.rb +0 -39
  79. data/test/column_test.rb +0 -27
  80. data/test/grid_test.rb +0 -43
  81. data/test/netzke_basepack_test.rb +0 -8
@@ -0,0 +1,13 @@
1
+ module Netzke
2
+ class Panel < Base
3
+ def self.js_extend_properties
4
+ {
5
+ :update_body_html => <<-END_OF_JAVASCRIPT.l,
6
+ function(html){
7
+ this.body.update(html);
8
+ }
9
+ END_OF_JAVASCRIPT
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,121 @@
1
+ module Netzke::Plugins
2
+ # Include this module into any widget where you want a "gear" tool button in the top toolbar
3
+ # which will triggger a modal window, which will load the ConfigurationPanel TabPanel-based
4
+ # widget, which in its turn will contain all the aggregatees specified in widget's "configuration_widgets"
5
+ # method (which *must* be defined)
6
+ module ConfigurationTool
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+
10
+ base.class_eval do
11
+ # replacing instance methods
12
+ [:config, :initial_aggregatees, :js_config].each{ |m| alias_method_chain m, :config_tool }
13
+
14
+ # replacing class methods
15
+ class << self
16
+ alias_method_chain :js_extend_properties, :config_tool
17
+ end
18
+
19
+ # API to commit the changes
20
+ api :commit
21
+ end
22
+
23
+ # if you include ConfigurationTool, you are supposed to provide configuration_widgets method which will returns an array of arrgeratees
24
+ # that will be included in the property window (each in its own tab or accordion pane)
25
+ raise "configuration_widgets method undefined" unless base.instance_methods.include?("configuration_widgets")
26
+ end
27
+
28
+ module ClassMethods
29
+ def js_extend_properties_with_config_tool
30
+ js_extend_properties_without_config_tool.merge({
31
+ :gear => <<-END_OF_JAVASCRIPT.l
32
+ function(){
33
+ var w = new Ext.Window({
34
+ title:'Config',
35
+ layout:'fit',
36
+ modal:true,
37
+ width: Ext.lib.Dom.getViewWidth() *0.9,
38
+ height: Ext.lib.Dom.getViewHeight() *0.9,
39
+ closeAction:'destroy',
40
+ buttons:[{
41
+ text:'OK',
42
+ disabled: !this.configurable,
43
+ tooltip: this.configurable ? null : "No dynamic configuration for this component",
44
+ handler:function(){
45
+ w.closeRes = 'OK';
46
+ w.close();
47
+ }
48
+ },{
49
+ text:'Cancel',
50
+ handler:function(){
51
+ w.closeRes = 'cancel';
52
+ w.close();
53
+ }
54
+ }]
55
+
56
+ });
57
+
58
+ w.show(null, function(){
59
+ this.loadAggregatee({id:"configuration_panel", container:w.id});
60
+ }, this);
61
+
62
+ w.on('close', function(){
63
+ if (w.closeRes == 'OK'){
64
+ var configurationPanel = this.getChildWidget('configuration_panel');
65
+ var panels = configurationPanel.getLoadedChildren();
66
+ var commitData = {};
67
+ Ext.each(panels, function(p){
68
+ if (p.getCommitData) {commitData[p.localId(configurationPanel)] = p.getCommitData();}
69
+ }, this);
70
+ configurationPanel.commit({commit_data:Ext.encode(commitData)});
71
+ }
72
+ }, this);
73
+ }
74
+ END_OF_JAVASCRIPT
75
+ })
76
+ end
77
+ end
78
+
79
+ def config_with_config_tool
80
+ orig_config = config_without_config_tool
81
+ return orig_config unless config_tool_needed?
82
+ orig_config.deep_merge({
83
+ :ext_config => {
84
+ :tools => orig_config[:ext_config][:tools].clone << "gear",
85
+ :header => true
86
+ }
87
+ })
88
+ end
89
+
90
+ def initial_aggregatees_with_config_tool
91
+ res = initial_aggregatees_without_config_tool
92
+
93
+ # Add the ConfigurationPanel as aggregatee, which in its turn aggregates widgets from the
94
+ # configuration_widgets method
95
+ res.merge!(:configuration_panel => {
96
+ :widget_class_name => 'ConfigurationPanel',
97
+ :items => configuration_widgets,
98
+ :late_aggregation => true
99
+ }) if config_tool_needed?
100
+
101
+ res
102
+ end
103
+
104
+ def tools_with_config_tool
105
+ tools = tools_without_config_tool
106
+ # Add the toolbutton
107
+ tools << 'gear' if config_tool_needed?
108
+ tools
109
+ end
110
+
111
+ def js_config_with_config_tool
112
+ orig_config = js_config_without_config_tool
113
+ orig_config.merge(:configurable => config[:persistent_config])
114
+ end
115
+
116
+ def config_tool_needed?
117
+ config_without_config_tool[:ext_config][:config_tool] || config_without_config_tool[:ext_config][:mode] == :config
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,105 @@
1
+ module Netzke
2
+ class PropertyEditor < FormPanel
3
+
4
+ def initialize(*args)
5
+ super
6
+ @widget = @passed_config[:widget]
7
+ end
8
+
9
+ def independent_config
10
+ res = super
11
+ res[:ext_config][:bbar] = %w{ restore_defaults }
12
+ res
13
+ end
14
+
15
+ def actions
16
+ {:restore_defaults => {:text => "Restore defaults"}}
17
+ end
18
+
19
+ def get_columns
20
+ fields = @widget.class.property_fields
21
+
22
+ for f in fields
23
+ f[:value] = @widget.flat_config(f[:name]).nil? ? f[:default] : @widget.flat_config(f[:name])
24
+ f[:xtype] = XTYPE_MAP[f[:type]]
25
+ f[:field_label] = f[:name].to_s.gsub("__", "/").humanize
26
+ end
27
+
28
+ fields
29
+ end
30
+
31
+ def self.js_extend_properties
32
+ {
33
+ :label_width => 200,
34
+
35
+ # Disable the 'gear' tool for now
36
+ :gear => <<-END_OF_JAVASCRIPT.l,
37
+ function(){
38
+ this.feedback("You can't configure property editor (yet)");
39
+ }
40
+ END_OF_JAVASCRIPT
41
+
42
+ :restore_defaults => <<-END_OF_JAVASCRIPT.l,
43
+ function(){
44
+ this.restoreDefaults();
45
+ }
46
+ END_OF_JAVASCRIPT
47
+
48
+ :get_commit_data => <<-END_OF_JAVASCRIPT.l
49
+ function(){
50
+ if (!this.getForm().isValid()) {this.getForm().reset()}; // if some fields are invalid, don't send anything
51
+ var values = this.getForm().getValues();
52
+ for (var k in values) {
53
+ if (values[k] == "") {values[k] = null}
54
+ }
55
+ return values;
56
+ }
57
+ END_OF_JAVASCRIPT
58
+ }
59
+ end
60
+
61
+ api :restore_defaults
62
+ def restore_defaults(params)
63
+ values = []
64
+ columns.each do |c|
65
+ init_config = @widget.flat_initial_config.detect{ |ic| ic[:name] == c[:name] }
66
+
67
+ if init_config.nil?
68
+ property_fields ||= @widget.class.property_fields
69
+ values << property_fields.detect{ |f| f[:name] == c[:name] }[:default]
70
+ else
71
+ values << init_config[:value]
72
+ end
73
+
74
+ end
75
+ {:set_form_values => values}
76
+ end
77
+
78
+ def commit(data)
79
+ fields = @widget.class.property_fields
80
+ data.each_pair do |property, value|
81
+ field = fields.detect{ |f| f[:name] == property.to_sym }
82
+ # default = @widget.config[property].nil? ? field[:default] : @widget.config[property]
83
+ default = @widget.flat_initial_config(property).nil? ? field[:default] : @widget.flat_initial_config(property)
84
+ # Only store the value in persistent config when it's different from the default one
85
+ if field[:type] == :boolean
86
+ # handle boolean type separately
87
+ value = value.to_b
88
+ @widget.persistent_config[property] = value ^ default ? value : nil
89
+ else
90
+ if field[:type] == :json
91
+ value = ActiveSupport::JSON.decode(value)
92
+ end
93
+
94
+ # Empty string means "null" for now...
95
+ # value = nil if value.blank?
96
+ # logger.debug "!!! value: #{value.inspect}\n"
97
+
98
+ @widget.persistent_config[property] = default == value ? nil : value
99
+ end
100
+ end
101
+ {}
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,126 @@
1
+ module Netzke
2
+ module PropertyEditorExtras
3
+ class HelperModel
4
+ def self.widget=(w)
5
+ @@widget = w
6
+ end
7
+
8
+ def self.widget
9
+ @@widget ||= raise RuntimeError, "No widget specified for PropertyEditorExtras::HelperModel"
10
+ end
11
+
12
+ def self.reflect_on_all_associations
13
+ []
14
+ end
15
+
16
+ def self.primary_key
17
+ "id"
18
+ end
19
+
20
+ def self.netzke_exposed_attributes
21
+ preferences = self.widget.flat_default_config
22
+ # preferences = NetzkePreference.find_all_for_widget(widget.name)
23
+ preferences.each { |p| p.reject!{ |k,v| k == :value}.merge!(:field_label => p[:name].to_s.gsub('__', "/").humanize) }
24
+ preferences
25
+ end
26
+
27
+ DEFAULTS_FOR_FIELD = {
28
+ :Fixnum => {
29
+ :xtype => :numberfield
30
+ },
31
+ :Boolean => {
32
+ :xtype => :xcheckbox,
33
+ :checked => true
34
+ },
35
+ :String => {
36
+ :xtype => :textfield
37
+ }
38
+ }
39
+
40
+ # DRY out!
41
+ def self.default_field_config(config)
42
+ type = config.delete(:type)
43
+
44
+ common = {
45
+ :field_label => config[:name].to_s.gsub('__', '_').humanize,
46
+ :hidden => config[:name] == :id
47
+ }
48
+
49
+ default = DEFAULTS_FOR_FIELD[type] || DEFAULTS_FOR_FIELD[:String] # fallback to plain textfield
50
+
51
+ res = default.merge(common).merge(config)
52
+ end
53
+
54
+ def self.find_by_id(*args)
55
+ self.new
56
+ end
57
+
58
+ def save
59
+ end
60
+
61
+ def errors
62
+ a = Array.new
63
+ def a.each_full
64
+ []
65
+ end
66
+ a
67
+ end
68
+
69
+ def to_array(columns)
70
+ res = []
71
+ for c in columns
72
+ method = c.is_a?(Symbol) ? c : c[:name]
73
+ value = send(method)
74
+ res << (value.is_a?(Array) || value.is_a?(Hash) ? value.to_json : value)
75
+ end
76
+ res
77
+ end
78
+
79
+ # somewhat sofisticated code to convert all NetzkePreferences for current widget into a hash ("un-flatten")
80
+ def attributes
81
+ prefs = NetzkePreference.find_all_for_widget(self.class.widget.id_name)
82
+ res = {}
83
+ prefs.each do |p|
84
+ tmp_res = {}
85
+ hsh_levels = p.name.split("__").map(&:to_sym)
86
+ hsh_levels.each do |level_prefix|
87
+ tmp_res[level_prefix] ||= level_prefix == hsh_levels.last ? p.normalized_value : {}
88
+ res[level_prefix] = tmp_res[level_prefix] if level_prefix == hsh_levels.first
89
+ tmp_res = tmp_res[level_prefix]
90
+ end
91
+ end
92
+ res
93
+ end
94
+
95
+ def method_missing(method_name, *args)
96
+ # Rails.logger.debug "!!! method_name: #{method_name.inspect}"
97
+ method_name = method_name.to_s
98
+ method_name_without_equal_sign = method_name.sub(/=$/, '')
99
+ NetzkePreference.widget_name = self.class.widget.id_name
100
+
101
+ if method_name =~ /=$/
102
+ # current_value = NetzkePreference[method_name_without_equal_sign] # may be nil
103
+ current_value = self.class.widget.flat_independent_config(method_name_without_equal_sign)
104
+
105
+ begin
106
+ new_value = ActiveSupport::JSON.decode(args.first) # TODO: provide feedback about this error
107
+ rescue ActiveSupport::JSON::ParseError
108
+ new_value = current_value
109
+ end
110
+
111
+ # default_value = self.class.widget.flat_default_config(method_name_without_equal_sign)
112
+ initial_value = self.class.widget.flat_initial_config(method_name_without_equal_sign)
113
+
114
+ new_value = nil if new_value == initial_value
115
+ # Rails.logger.debug "!!! new_value: #{new_value.inspect}"
116
+ NetzkePreference[method_name_without_equal_sign] = new_value
117
+ else
118
+ res = self.class.widget.flat_independent_config(method_name_without_equal_sign)
119
+ res = ActiveSupport::JSON.encode(res) if res.is_a?(Array) || res.is_a?(Hash)
120
+ res
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,62 @@
1
+ module Netzke
2
+ # SearchPanel
3
+ #
4
+ # FormPanel-based widget that allows create configurable searchlogic-compatible searches.
5
+ # Pretty much work in progress.
6
+ class SearchPanel < FormPanel
7
+ # Something like [:equals, :greater_than_or_equal_to, :does_not_equal, :less_than, :less_than_or_equal_to, :greater_than, :ends_with, :like, :begins_with, :empty, :null]
8
+ CONDITIONS = [:COMPARISON_CONDITIONS, :WILDCARD_CONDITIONS, :BOOLEAN_CONDITIONS].inject([]){|r, c| r + Searchlogic::NamedScopes::Conditions.const_get(c).keys}
9
+
10
+ def default_config
11
+ super.merge({
12
+ :data_class_name => @passed_config[:search_class_name]
13
+ })
14
+ end
15
+
16
+ def default_columns
17
+ res = super
18
+
19
+ res.map! do |f|
20
+ norm_column = normalize_column(f)
21
+ norm_column.merge!({
22
+ :condition => "equals"
23
+ })
24
+ norm_column.merge!(:hidden => true) if norm_column[:name].to_s.index("__")
25
+
26
+ norm_column
27
+ end
28
+
29
+ res
30
+ end
31
+
32
+ # columns to be displayed by the FieldConfigurator (which is GridPanel-based)
33
+ def self.config_columns
34
+ [
35
+ {:name => :hidden, :type => :boolean, :editor => :checkbox, :width => 50},
36
+ {:name => :name, :type => :string, :editor => :combobox},
37
+ {:name => :condition, :type => :string, :editor => {:xtype => :combobox, :options => CONDITIONS}},
38
+ {:name => :field_label, :type => :string},
39
+ {:name => :xtype, :type => :string},
40
+ {:name => :value, :type => :string},
41
+ ]
42
+ end
43
+
44
+ # tweaking the form fields at the last moment
45
+ def js_config
46
+ super.merge({
47
+ :clmns => columns.map{ |c| c.merge({
48
+ :field_label => "#{c[:field_label] || c[:name]} #{c[:condition]}".humanize,
49
+ :name => "#{c[:name]}_#{c[:condition]}"
50
+ })}
51
+ })
52
+ end
53
+
54
+ # we need to correct the queries to cut off the condition suffixes, otherwise the FormPanel gets confused
55
+ def get_combobox_options(params)
56
+ column_name = params[:column]
57
+ CONDITIONS.each { |c| column_name.sub!(/_#{c}$/, "") }
58
+ super(:column => column_name)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,160 @@
1
+ module Netzke
2
+ # TabPanel
3
+ #
4
+ # Features:
5
+ # * Dynamically loads widgets for the tabs that get activated for the first time
6
+ # * Is loaded along with the active widget - saves a request to the server
7
+ # * Provides the method markTabsOutdated to mark all inactive tabs as 'outdated', and calls "update" method on widgets in tabs when they get activated
8
+ #
9
+ # TODO:
10
+ # * Stores the last active tab in persistent_config
11
+ # * Introduce a second or two delay before informing the server about a tab switched
12
+ #
13
+ class TabPanel < Base
14
+ api :api_activate_tab
15
+
16
+ def self.js_base_class
17
+ "Ext.TabPanel"
18
+ end
19
+
20
+ def self.js_extend_properties
21
+ {
22
+ :id_delimiter => "___", # the default was "__", which conflicts with Netzke's double underscore notation
23
+ :defaults => {:layout => 'fit'}, # all tabs will be Ext.Panel-s with layout 'fit' ("fit-panels")
24
+
25
+ :init_component => <<-END_OF_JAVASCRIPT.l,
26
+ function(){
27
+ Ext.netzke.cache.#{short_widget_class_name}.superclass.initComponent.call(this);
28
+
29
+ this.on('tabchange', function(self, tab){this.loadItemWidget(tab)}, this);
30
+ }
31
+ END_OF_JAVASCRIPT
32
+
33
+ :mark_tabs_outdated => <<-END_OF_JAVASCRIPT.l,
34
+ function(){
35
+ this.items.each(function(i){
36
+ if (this.getActiveTab() != i){
37
+ i.outdated = true
38
+ }
39
+ }, this);
40
+ }
41
+ END_OF_JAVASCRIPT
42
+
43
+ # bulkExecute in active tab
44
+ :execute_in_active_tab => <<-END_OF_JAVASCRIPT.l,
45
+ function(commands){
46
+ this.getActiveTab().getWidget().bulkExecute(commands);
47
+ }
48
+ END_OF_JAVASCRIPT
49
+
50
+ :get_loaded_children => <<-END_OF_JAVASCRIPT.l,
51
+ function(){
52
+ var res = [];
53
+ this.items.each(function(tab){
54
+ var kid = tab.getWidget();
55
+ if (kid) { res.push(kid) }
56
+ }, this);
57
+ return res;
58
+ }
59
+ END_OF_JAVASCRIPT
60
+
61
+ # loads widget into the panel if it wasn't loaded yet
62
+ :load_item_widget => <<-END_OF_JAVASCRIPT.l
63
+ function(panel) {
64
+ if (!panel.getWidget()) {
65
+ if (preloadedItemConfig = this.initialConfig[panel.widget.camelize()+"Config"]){
66
+ // preloaded widget only needs to be instantiated, as its class and configuration have already been loaded
67
+ panel.add(new Ext.netzke.cache[preloadedItemConfig.widgetClassName](preloadedItemConfig));
68
+ panel.doLayout(); // always needed after adding a component
69
+ } else {
70
+ // load the widget from the server
71
+ this.loadAggregatee({id:panel.widget, container:panel.id});
72
+ }
73
+ }
74
+
75
+ // inform the server about active tab changed
76
+ this.apiActivateTab({tab:panel.widget});
77
+
78
+ // call "update" on the widget
79
+ if (panel.outdated) {
80
+ delete panel.outdated;
81
+ var widget = panel.getWidget();
82
+ if (widget && widget.update) {widget.update.call(widget)};
83
+ }
84
+ }
85
+ END_OF_JAVASCRIPT
86
+ }
87
+ end
88
+
89
+ def items
90
+ @items ||= config[:items]
91
+ end
92
+
93
+ def js_config
94
+ super.merge({
95
+ :items => fit_panels,
96
+ :active_tab => id_name + '_active' # id of the fit panel that is active
97
+ })
98
+ end
99
+
100
+ # some configuration normalization
101
+ def initialize(*args)
102
+ super
103
+
104
+ # to remove duplicated active tabs
105
+ first_active = nil
106
+
107
+ items.each_with_index do |item, i|
108
+ # if the item is provided without a name, give it a generated name
109
+ item[:name] ||= "item#{i}"
110
+
111
+ # remove duplicated "active" configuration
112
+ if item[:active]
113
+ if first_active.nil?
114
+ first_active = item.name
115
+ else
116
+ item[:active] = nil
117
+ end
118
+ end
119
+ end
120
+
121
+ # the first tab is forced to become active, if none was configured as active
122
+ items.first[:active] = true and first_active = items.first.name if first_active.nil?
123
+
124
+ widget_session[:active_tab] = first_active
125
+ end
126
+
127
+ # the items are late aggregatees, besides the one that is configured active
128
+ def initial_aggregatees
129
+ res = {}
130
+ items.each_with_index do |item, i|
131
+ item[:late_aggregation] = !item[:active] && !item[:preloaded]
132
+ res.merge!(item[:name].to_sym => item)
133
+ end
134
+ res
135
+ end
136
+
137
+ # "Fit panels" - Panels with layout 'fit' that serve as containers for (dynamically) loaded widgets
138
+ def fit_panels
139
+ res = []
140
+ items.each_with_index do |item, i|
141
+ item_config = {
142
+ :id => item[:active] && id_name + '_active',
143
+ :title => item[:title] || (item[:name] && item[:name].humanize),
144
+ :widget => item[:name] # to know which fit-panel will load which widget
145
+ }
146
+ res << item_config
147
+ end
148
+ res
149
+ end
150
+
151
+ def api_activate_tab(params)
152
+ widget_session[:active_tab] = params[:tab]
153
+ {}
154
+ end
155
+
156
+ def get_active_tab
157
+ aggregatee_instance(widget_session[:active_tab])
158
+ end
159
+ end
160
+ end