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,56 @@
1
+ require 'acts_as_list'
2
+ class NetzkeAutoColumn < ActiveRecord::Base
3
+
4
+ acts_as_list
5
+ default_scope :order => "position"
6
+
7
+ # Returns an array of column configuration hashes (without the "id" attribute)
8
+ def self.all_columns
9
+ self.all.map do |c|
10
+ column_hash = c.attributes.reject{ |k,v| k == 'id' }
11
+ column_hash.each_pair do |k,v|
12
+ # try to detect JSON format
13
+ begin
14
+ normalized_value = v.is_a?(String) ? ActiveSupport::JSON.decode(v) : v
15
+ rescue ActiveSupport::JSON::ParseError
16
+ normalized_value = v
17
+ end
18
+ column_hash[k] = normalized_value
19
+ end
20
+ column_hash
21
+ end
22
+ end
23
+
24
+ # Build the table with columns for this widget
25
+ def self.rebuild_table
26
+ connection.drop_table('netzke_auto_columns') if table_exists?
27
+
28
+ normalized_config_columns = []
29
+
30
+ @@widget.class.config_columns.each do |mc|
31
+ column_hash = mc.is_a?(Symbol) ? {:name => mc} : mc
32
+ column_hash[:type] ||= :string
33
+ normalized_config_columns << column_hash
34
+ end
35
+
36
+ # create the table with the fields
37
+ self.connection.create_table('netzke_auto_columns') do |t|
38
+ normalized_config_columns.each do |mc|
39
+ t.column mc[:name], mc[:type], :default => mc[:default]
40
+ end
41
+ t.column :position, :integer
42
+ end
43
+
44
+ # populate the table with data
45
+ NetzkeAutoColumn.create @@widget.normalized_columns.map(&:deebeefy_values)
46
+
47
+ end
48
+
49
+ def self.widget=(widget)
50
+ @@widget = widget
51
+ if Netzke::Base.session["netzke_auto_column_last_widget"] != @@widget.id_name
52
+ rebuild_table
53
+ Netzke::Base.session["netzke_auto_column_last_widget"] = @@widget.id_name
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,113 @@
1
+ module Netzke
2
+ # == AccordionPanel
3
+ #
4
+ # == Features:
5
+ # * Dynamically loads widgets for the panels that get expanded for the first time
6
+ # * Is loaded along with the active widget - saves a request to the server
7
+ #
8
+ # Future features:
9
+ # * Stores the last active panel in persistent_config
10
+ class AccordionPanel < Base
11
+
12
+ # JavaScript part
13
+ def self.js_extend_properties
14
+ {
15
+ :layout => 'accordion',
16
+ :defaults => {:layout => 'fit'},
17
+ :init_component => <<-END_OF_JAVASCRIPT.l,
18
+ function(){
19
+ Ext.netzke.cache.#{short_widget_class_name}.superclass.initComponent.call(this);
20
+
21
+ // Set events
22
+ this.items.each(function(i){
23
+ // Set the expand event
24
+ i.on('expand', this.loadItemWidget, this);
25
+
26
+ // If not collapsed, add the active aggregatee (item) into it
27
+ if (!i.collapsed) {
28
+ var preloadedItemConfig = this[i.widget.camelize(true) + "Config"];
29
+ i.add(new Ext.netzke.cache[preloadedItemConfig.widgetClassName](preloadedItemConfig));
30
+ i.doLayout(); // always needed after adding a component
31
+ }
32
+ }, this);
33
+ }
34
+ END_OF_JAVASCRIPT
35
+
36
+ # Loads widget into the panel if it wasn't loaded yet
37
+ :load_item_widget => <<-END_OF_JAVASCRIPT.l,
38
+ function(panel) {
39
+ // if (!panel.getWidget()) panel.loadWidget(this.id + "__" + panel.widget + "__get_widget");
40
+ var preloadedItemConfig = this[panel.widget.camelize(true) + "Config"];
41
+
42
+ if (preloadedItemConfig){
43
+ // preloaded widget only needs to be instantiated, as its class and configuration have already been loaded
44
+ panel.add(new Ext.netzke.cache[preloadedItemConfig.widgetClassName](preloadedItemConfig));
45
+ panel.doLayout(); // always needed after adding a component
46
+ } else {
47
+ // load the widget from the server
48
+ this.loadAggregatee({id:panel.widget, container:panel.id});
49
+ }
50
+
51
+ }
52
+ END_OF_JAVASCRIPT
53
+ }
54
+ end
55
+
56
+ # Some normalization of config
57
+ def initialize(*args)
58
+ super
59
+
60
+ seen_active = false
61
+
62
+ config[:items].each_with_index do |item, i|
63
+ # if some items are provided without names, give them generated names
64
+ item[:name] ||= "item#{i}"
65
+
66
+ # remove duplucated :active configuration
67
+ if item[:active]
68
+ item[:active] = nil if seen_active
69
+ seen_active ||= true
70
+ end
71
+ end
72
+ end
73
+
74
+ # Returns items configs
75
+ def items
76
+ @items ||= config[:items]
77
+ end
78
+
79
+ # Provides configs for fit panels (which will effectively be accordion panels)
80
+ def js_config
81
+ super.merge({
82
+ # these "items" are not related to the "items" of the config, rather these are the items required by the the accordion panel
83
+ :items => fit_panels
84
+ })
85
+ end
86
+
87
+ # "Fit-panels" - panels of layout 'fit' (effectively the accordion panels) that will contain the widgets ("items")
88
+ def fit_panels
89
+ res = []
90
+ config[:items].each_with_index do |item, i|
91
+ res << {
92
+ # :id => item[:active] && id_name + '_active', # to mark the fit-panel which will contain the active widget
93
+ :title => item[:title] || (item[:name] && item[:name].to_s.humanize),
94
+ :widget => item[:name], # to know which fit panel will load which widget
95
+ :collapsed => !(item[:active] || false)
96
+ }
97
+ end
98
+ res
99
+ end
100
+
101
+ # All items become *late* aggregatees, besides the ones that are marked "active"
102
+ def initial_aggregatees
103
+ res = {}
104
+ config[:items].each_with_index do |item, i|
105
+ item[:late_aggregation] = !item[:active]
106
+ res.merge!(item[:name].to_sym => item)
107
+ end
108
+ res
109
+ end
110
+
111
+
112
+ end
113
+ end
@@ -0,0 +1,104 @@
1
+ module Netzke::ActiveRecord
2
+ # Provides extensions to all ActiveRecord-based classes
3
+ module Basepack
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ # Allow nested association access (assocs separated by "." or "__"), e.g.: proxy_service.asset__gui_folder__name
9
+ # Example:
10
+ #
11
+ # Book.first.genre__name = 'Fantasy'
12
+ #
13
+ # is the same as:
14
+ #
15
+ # Book.first.genre = Genre.find_by_name('Fantasy')
16
+ #
17
+ # The result - easier forms and grids that handle nested models: simply specify column/field name as "genre__name".
18
+ def method_missing(method, *args, &block)
19
+ # if refering to a column, just pass it to the original method_missing
20
+ return super if self.class.column_names.include?(method.to_s)
21
+
22
+ split = method.to_s.split(/\.|__/)
23
+ if split.size > 1
24
+ if split.last =~ /=$/
25
+ if split.size == 2
26
+ # search for association and assign it to self
27
+ assoc = self.class.reflect_on_association(split.first.to_sym)
28
+ assoc_method = split.last.chop
29
+ if assoc
30
+ begin
31
+ assoc_instance = assoc.klass.send("find_by_#{assoc_method}", *args)
32
+ rescue NoMethodError
33
+ assoc_instance = nil
34
+ logger.debug "!!! no find_by_#{assoc_method} method for class #{assoc.klass.name}\n"
35
+ end
36
+ if (assoc_instance)
37
+ self.send("#{split.first}=", assoc_instance)
38
+ else
39
+ logger.debug "!!! Couldn't find association #{split.first} by #{assoc_method} '#{args.first}'"
40
+ end
41
+ else
42
+ super
43
+ end
44
+ else
45
+ super
46
+ end
47
+ else
48
+ res = self
49
+ split.each do |m|
50
+ if res.respond_to?(m)
51
+ res = res.send(m) unless res.nil?
52
+ else
53
+ res.nil? ? nil : super
54
+ end
55
+ end
56
+ res
57
+ end
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+
65
+ def options_for(column, query = nil)
66
+ # First, check if we have options for this class and column defined in persistent storage
67
+ NetzkePreference.widget_name = self.name
68
+ options = NetzkePreference[:combobox_options] || {}
69
+ if options[column]
70
+ options[column].select{ |o| o.index(/^#{query}/) }
71
+ elsif respond_to?("#{column}_combobox_options")
72
+ # AR class provides the choices itself
73
+ send("#{column}_combobox_options", query)
74
+ else
75
+ # Returns all unique values for a column, filtered with <tt>query</tt>
76
+ if (assoc_name, *assoc_method = column.split('__')).size > 1
77
+ # column is an association column
78
+ assoc_method = assoc_method.join('__') # in case we get something like country__continent__name
79
+ association = reflect_on_association(assoc_name.to_sym) || raise(NameError, "Association #{assoc_name} not known for class #{name}")
80
+ association.klass.options_for(assoc_method, query)
81
+ else
82
+ column = assoc_name
83
+ if self.column_names.include?(column)
84
+ # it's simply a column in the table
85
+ records = query.nil? ? find_by_sql("select distinct #{column} from #{table_name}") : find_by_sql("select distinct #{column} from #{table_name} where #{column} like '#{query}%'")
86
+ records.map{|r| r.send(column)}
87
+ else
88
+ # it's a "virtual" column - the least effective search
89
+ records = self.find(:all).map{|r| r.send(column)}.uniq
90
+ query.nil? ? records : records.select{|r| r.index(/^#{query}/)}
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ # Extend ActiveRecord
102
+ ActiveRecord::Base.class_eval do
103
+ include Netzke::ActiveRecord::Basepack
104
+ end
@@ -0,0 +1,21 @@
1
+ module Netzke::ActiveRecord
2
+ # Provides extensions to those ActiveRecord-based models that provide data to the "data accessor" widgets,
3
+ # like GridPanel, FormPanel, etc
4
+ module DataAccessor
5
+ # Transforms a record to array of values according to the passed columns.
6
+ def to_array(columns)
7
+ res = []
8
+ for c in columns
9
+ nc = c.is_a?(Symbol) ? {:name => c} : c
10
+ begin
11
+ res << send(nc[:name]) unless nc[:excluded]
12
+ rescue
13
+ # So that we don't crash at a badly configured column
14
+ res << "UNDEF"
15
+ end
16
+ end
17
+ res
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,325 @@
1
+ module Netzke
2
+ # == BasicApp
3
+ # Basis for a Ext.Viewport-based application
4
+ #
5
+ # Features:
6
+ # * dynamic loading of widgets
7
+ # * authentification support
8
+ # * browser history support (press the "Back"-button to go to the previously loaded widget)
9
+ # * FeedbackGhost-powered feedback
10
+ # * aggregation of widget's own menus
11
+ # * masquerade support
12
+ # * AJAX activity indicator
13
+ class BasicApp < Base
14
+ module ClassMethods
15
+
16
+ def js_base_class
17
+ "Ext.Viewport"
18
+ end
19
+
20
+ # Global BasicApp configuration
21
+ def config
22
+ set_default_config({
23
+ :logout_url => "/logout" # default logout url
24
+ })
25
+ end
26
+
27
+ def js_panels
28
+ # In status bar we want to show what we are masquerading as
29
+ if session[:masq_user]
30
+ user = User.find(session[:masq_user])
31
+ masq = %Q{user "#{user.login}"}
32
+ elsif session[:masq_role]
33
+ role = Role.find(session[:masq_role])
34
+ masq = %Q{role "#{role.name}"}
35
+ end
36
+
37
+ [{
38
+ :id => 'main-panel',
39
+ :region => 'center',
40
+ :layout => 'fit'
41
+ },{
42
+ :id => 'main-toolbar',
43
+ :xtype => 'toolbar',
44
+ :region => 'north',
45
+ :height => 25
46
+ # :items => ["-"]
47
+ },{
48
+ :id => 'main-statusbar',
49
+ :xtype => 'statusbar',
50
+ :region => 'south',
51
+ :statusAlign => 'right',
52
+ :busyText => 'Busy...',
53
+ :default_text => masq.nil? ? "Ready #{"(config mode)" if session[:config_mode]}" : "Masquerading as #{masq}",
54
+ :default_icon_cls => ""
55
+ }]
56
+ end
57
+
58
+ def js_extend_properties
59
+ {
60
+ :layout => 'border',
61
+
62
+ :panels => js_panels,
63
+
64
+ :init_component => <<-END_OF_JAVASCRIPT.l,
65
+ function(){
66
+ this.items = this.panels; // a bit weird, but working; can't assign it straight
67
+
68
+ Ext.netzke.cache.BasicApp.superclass.initComponent.call(this);
69
+
70
+ // If we are given a token, load the corresponding widget, otherwise load the last loaded widget
71
+ var currentToken = Ext.History.getToken();
72
+ if (currentToken != "") {
73
+ this.processHistory(currentToken)
74
+ } else {
75
+ var lastLoaded = this.initialConfig.widgetToLoad; // passed from the server
76
+ if (lastLoaded) Ext.History.add(lastLoaded);
77
+ }
78
+
79
+ Ext.History.on('change', this.processHistory, this);
80
+
81
+ // Hosted menus
82
+ this.menus = {};
83
+
84
+ // Setting the "busy" indicator for Ajax requests
85
+ Ext.Ajax.on('beforerequest', function(){this.findById('main-statusbar').showBusy()}, this);
86
+ Ext.Ajax.on('requestcomplete', function(){this.findById('main-statusbar').hideBusy()}, this);
87
+ Ext.Ajax.on('requestexception', function(){this.findById('main-statusbar').hideBusy()}, this);
88
+ }
89
+ END_OF_JAVASCRIPT
90
+
91
+ :host_menu => <<-END_OF_JAVASCRIPT.l,
92
+ function(menu, owner){
93
+ var toolbar = this.findById('main-toolbar');
94
+ if (!this.menus[owner.id]) this.menus[owner.id] = [];
95
+ Ext.each(menu, function(item) {
96
+ // var newMenu = new Ext.Toolbar.Button(item);
97
+ // var position = toolbar.items.getCount() - 2;
98
+ // position = position < 0 ? 0 : position;
99
+ // toolbar.insertButton(position, newMenu);
100
+
101
+ toolbar.add(item);
102
+ // this.menus[owner.id].push(newMenu); // TODO: remember the menus from this owner in some other way
103
+ }, this);
104
+ }
105
+ END_OF_JAVASCRIPT
106
+
107
+ :unhost_menu => <<-END_OF_JAVASCRIPT.l,
108
+ function(owner){
109
+ // var toolbar = this.findById('main-toolbar');
110
+ // if (this.menus[owner.id]) {
111
+ // Ext.each(this.menus[owner.id], function(menu){
112
+ // toolbar.items.remove(menu); // remove the item from the toolbar
113
+ // menu.destroy(); // ... and destroy it
114
+ // });
115
+ // }
116
+ }
117
+ END_OF_JAVASCRIPT
118
+
119
+ :logout => <<-END_OF_JAVASCRIPT.l,
120
+ function(){
121
+ window.location = "#{config[:logout_url]}"
122
+ }
123
+ END_OF_JAVASCRIPT
124
+
125
+ # Event handler for history change
126
+ :process_history => <<-END_OF_JAVASCRIPT.l,
127
+ function(token){
128
+ if (token){
129
+ this.loadAggregatee({id:token, container:'main-panel'});
130
+ } else {
131
+ }
132
+ }
133
+ END_OF_JAVASCRIPT
134
+
135
+ :instantiate_aggregatee => <<-END_OF_JAVASCRIPT.l,
136
+ function(config){
137
+ this.findById('main-panel').instantiateChild(config);
138
+ }
139
+ END_OF_JAVASCRIPT
140
+
141
+ # Loads widget by name
142
+ :app_load_widget => <<-END_OF_JAVASCRIPT.l,
143
+ function(name){
144
+ Ext.History.add(name);
145
+ }
146
+ END_OF_JAVASCRIPT
147
+
148
+ # Loads widget by action
149
+ :load_widget_by_action => <<-END_OF_JAVASCRIPT.l,
150
+ function(action){
151
+ this.appLoadWidget(action.widget || action.name);
152
+ }
153
+ END_OF_JAVASCRIPT
154
+
155
+ # Masquerade selector window
156
+ :show_masquerade_selector => <<-END_OF_JAVASCRIPT.l,
157
+ function(){
158
+ var w = new Ext.Window({
159
+ title: 'Masquerade as',
160
+ modal: true,
161
+ width: Ext.lib.Dom.getViewWidth() * 0.6,
162
+ height: Ext.lib.Dom.getViewHeight() * 0.6,
163
+ layout: 'fit',
164
+ closeAction :'destroy',
165
+ buttons: [{
166
+ text: 'Select',
167
+ handler : function(){
168
+ if (role = w.getWidget().masquerade.role) {
169
+ Ext.Msg.confirm("Masquerading as a role", "Individual preferences for all users with this role will get overwritten as you make changes. Continue?", function(btn){
170
+ if (btn === 'yes') {
171
+ w.close();
172
+ }
173
+ });
174
+ } else {
175
+ w.close();
176
+ }
177
+ },
178
+ scope:this
179
+ },{
180
+ text:'Turn off masquerading',
181
+ handler:function(){
182
+ this.masquerade = {};
183
+ w.close();
184
+ },
185
+ scope:this
186
+ },{
187
+ text:'Cansel',
188
+ handler:function(){
189
+ w.hide();
190
+ },
191
+ scope:this
192
+ }],
193
+ listeners : {close: {fn: function(){
194
+ this.masqAs(this.masquerade || w.getWidget().masquerade || {});
195
+ }, scope: this}}
196
+ });
197
+
198
+ w.show(null, function(){
199
+ this.loadAggregatee({id:"masqueradeSelector", container:w.id})
200
+ }, this);
201
+
202
+ }
203
+ END_OF_JAVASCRIPT
204
+
205
+ # Masquerade as...
206
+ :masq_as => <<-END_OF_JAVASCRIPT.l
207
+ function(masqConfig){
208
+ params = {};
209
+
210
+ if (masqConfig.user) {
211
+ params.user = masqConfig.user
212
+ }
213
+
214
+ if (masqConfig.role) {
215
+ params.role = masqConfig.role
216
+ }
217
+
218
+ this.masqueradeAs(params);
219
+
220
+ }
221
+ END_OF_JAVASCRIPT
222
+ }
223
+ end
224
+ end
225
+
226
+ extend ClassMethods
227
+
228
+ # Set the Logout button if Netzke::Base.user is set
229
+ def menu
230
+ res = []
231
+ user = Netzke::Base.user
232
+ if !user.nil?
233
+ user_name = user.respond_to?(:name) ? user.name : user.login # try to display user's name, fallback to login
234
+ res << "->" <<
235
+ {
236
+ :text => "#{user_name}",
237
+ :menu => user_menu
238
+ }
239
+ else
240
+ res << "->" <<
241
+ {
242
+ :text => "Login",
243
+ :handler => <<-END_OF_JAVASCRIPT.l,
244
+ function(){
245
+ window.location = "/login"
246
+ }
247
+ END_OF_JAVASCRIPT
248
+ :scope => this
249
+ }
250
+ end
251
+ res
252
+ end
253
+
254
+ def user_menu
255
+ ['logout']
256
+ end
257
+
258
+ def initialize(*args)
259
+ super
260
+
261
+ if session[:netzke_just_logged_in] || session[:netzke_just_logged_out]
262
+ session[:config_mode] = false
263
+ session[:masq_user] = session[:masq_roles] = nil
264
+ end
265
+
266
+ strong_children_config.deep_merge!({:ext_config => {:mode => :config}}) if session[:config_mode]
267
+ end
268
+
269
+ #
270
+ # Available actions
271
+ #
272
+ def actions
273
+ {
274
+ :masquerade_selector => {:text => "Masquerade as ...", :fn => "showMasqueradeSelector"},
275
+ :toggle_config_mode => {:text => "#{session[:config_mode] ? "Leave" : "Enter"} config mode", :fn => "toggleConfigMode"},
276
+ :logout => {:text => "Log out", :fn => "logout"}
277
+ }
278
+ end
279
+
280
+
281
+ # Html required for Ext.History to work
282
+ def js_widget_html
283
+ super << %Q{
284
+ <form id="history-form" class="x-hidden">
285
+ <input type="hidden" id="x-history-field" />
286
+ <iframe id="x-history-frame"></iframe>
287
+ </form>
288
+ }
289
+ end
290
+
291
+ # We rely on the FeedbackGhost (to not need to implement our own feedback management)
292
+ def initial_aggregatees
293
+ {:feedback_ghost => {:widget_class_name => "FeedbackGhost"}}
294
+ end
295
+
296
+ # Besides instantiating ourselves, also instantiate the FeedbackGhost
297
+ def js_widget_instance
298
+ <<-END_OF_JAVASCRIPT << super
299
+ new Ext.netzke.cache['FeedbackGhost']({id:'feedback_ghost'})
300
+ // Initialize history (can't say why it's not working well inside the appLoaded handler)
301
+ Ext.History.init();
302
+ END_OF_JAVASCRIPT
303
+ end
304
+
305
+ #
306
+ # Interface section
307
+ #
308
+
309
+ api :toggle_config_mode
310
+ def toggle_config_mode(params)
311
+ session = Netzke::Base.session
312
+ session[:config_mode] = !session[:config_mode]
313
+ {:js => "window.location.reload();"}
314
+ end
315
+
316
+ api :masquerade_as
317
+ def masquerade_as(params)
318
+ session = Netzke::Base.session
319
+ session[:masq_role] = params[:role]
320
+ session[:masq_user] = params[:user]
321
+ {:js => "window.location.reload()"}
322
+ end
323
+
324
+ end
325
+ end