skozlov-netzke-basepack 0.1.1.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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