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.
- data/.autotest +1 -0
- data/.gitignore +5 -0
- data/LICENSE +2 -19
- data/README.rdoc +87 -0
- data/Rakefile +28 -12
- data/TODO.rdoc +7 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +3 -0
- data/init.rb +0 -1
- data/javascripts/basepack.js +839 -49
- data/lib/app/models/netzke_auto_column.rb +56 -0
- data/lib/netzke/accordion_panel.rb +113 -0
- data/lib/netzke/active_record/basepack.rb +104 -0
- data/lib/netzke/active_record/data_accessor.rb +21 -0
- data/lib/netzke/basic_app.rb +325 -0
- data/lib/netzke/border_layout_panel.rb +128 -0
- data/lib/netzke/configuration_panel.rb +24 -0
- data/lib/netzke/data_accessor.rb +71 -0
- data/lib/netzke/ext.rb +6 -0
- data/lib/netzke/field_model.rb +131 -0
- data/lib/netzke/fields_configurator.rb +95 -0
- data/lib/netzke/form_panel.rb +214 -0
- data/lib/netzke/form_panel_api.rb +74 -0
- data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
- data/lib/netzke/form_panel_js.rb +129 -0
- data/lib/netzke/grid_panel.rb +442 -0
- data/lib/netzke/grid_panel_api.rb +352 -0
- data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
- data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
- data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
- data/lib/netzke/grid_panel_js.rb +721 -0
- data/lib/netzke/panel.rb +13 -0
- data/lib/netzke/plugins/configuration_tool.rb +121 -0
- data/lib/netzke/property_editor.rb +105 -0
- data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
- data/lib/netzke/search_panel.rb +62 -0
- data/lib/netzke/tab_panel.rb +160 -0
- data/lib/netzke/table_editor.rb +118 -0
- data/lib/netzke/tree_panel.rb +73 -0
- data/lib/netzke/wrapper.rb +42 -0
- data/lib/netzke-basepack.rb +9 -15
- data/netzke-basepack.gemspec +147 -20
- data/stylesheets/basepack.css +26 -0
- data/test/app_root/app/models/book.rb +1 -1
- data/test/app_root/config/environment.rb +1 -0
- data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
- data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
- data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
- data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
- data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
- data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
- data/test/test_helper.rb +1 -2
- data/test/unit/accordion_panel_test.rb +20 -0
- data/test/unit/active_record_basepack_test.rb +54 -0
- data/test/unit/grid_panel_test.rb +43 -0
- data/test/unit/helper_model_test.rb +30 -0
- data/test/unit/netzke_basepack_test.rb +4 -0
- data/test/unit/tab_panel_test.rb +21 -0
- metadata +96 -72
- data/CHANGELOG +0 -14
- data/Manifest +0 -65
- data/README.mdown +0 -18
- data/generators/netzke_basepack/USAGE +0 -8
- data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
- data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
- data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
- data/lib/app/models/netzke_grid_column.rb +0 -23
- data/lib/netzke/accordion.rb +0 -11
- data/lib/netzke/ar_ext.rb +0 -163
- data/lib/netzke/column.rb +0 -43
- data/lib/netzke/container.rb +0 -81
- data/lib/netzke/grid.rb +0 -120
- data/lib/netzke/grid_interface.rb +0 -156
- data/lib/netzke/grid_js_builder.rb +0 -276
- data/lib/netzke/preference_grid.rb +0 -43
- data/lib/netzke/properties_tool.rb +0 -66
- data/lib/netzke/property_grid.rb +0 -60
- data/test/ar_ext_test.rb +0 -39
- data/test/column_test.rb +0 -27
- data/test/grid_test.rb +0 -43
- data/test/netzke_basepack_test.rb +0 -8
data/lib/netzke/panel.rb
ADDED
@@ -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
|