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.
- 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
|