netzke-core 0.6.4 → 0.6.5

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 (76) hide show
  1. data/CHANGELOG.rdoc +19 -0
  2. data/README.markdown +43 -0
  3. data/TODO +1 -5
  4. data/app/controllers/netzke_controller.rb +47 -15
  5. data/config/database.yml +2 -0
  6. data/features/component_loader.feature +6 -1
  7. data/features/composition.feature +2 -0
  8. data/features/js_include.feature +18 -0
  9. data/features/nested_views.feature +9 -0
  10. data/features/persistence.feature +6 -4
  11. data/features/support/paths.rb +3 -0
  12. data/javascripts/core.js +166 -519
  13. data/javascripts/ext.js +355 -0
  14. data/javascripts/touch.js +47 -0
  15. data/lib/netzke/actions.rb +31 -38
  16. data/lib/netzke/base.rb +48 -6
  17. data/lib/netzke/composition.rb +52 -63
  18. data/lib/netzke/configuration.rb +6 -2
  19. data/lib/netzke/core/version.rb +2 -2
  20. data/lib/netzke/core.rb +22 -15
  21. data/lib/netzke/javascript/scopes.rb +39 -0
  22. data/lib/netzke/javascript.rb +145 -114
  23. data/lib/netzke/railz/action_view_ext/ext.rb +59 -0
  24. data/lib/netzke/railz/action_view_ext/touch.rb +50 -0
  25. data/lib/netzke/railz/action_view_ext.rb +86 -0
  26. data/lib/netzke/railz/controller_extensions.rb +33 -0
  27. data/lib/netzke/{rails → railz}/routes.rb +0 -0
  28. data/lib/netzke/railz.rb +3 -0
  29. data/lib/netzke/session.rb +18 -3
  30. data/lib/netzke/state.rb +42 -15
  31. data/lib/netzke/stylesheets.rb +23 -8
  32. data/lib/netzke-core.rb +23 -16
  33. data/netzke-core.gemspec +52 -10
  34. data/spec/component/base_spec.rb +11 -0
  35. data/spec/component/javascript_spec.rb +3 -2
  36. data/spec/component/state_spec.rb +18 -0
  37. data/spec/spec_helper.rb +1 -1
  38. data/test/rails_app/Gemfile +3 -2
  39. data/test/rails_app/Gemfile.lock +73 -71
  40. data/test/rails_app/app/components/component_loader.rb +39 -4
  41. data/test/rails_app/app/components/{custom.css → component_with_custom_css/stylesheets/custom.css} +0 -0
  42. data/test/rails_app/app/components/component_with_custom_css.rb +2 -2
  43. data/test/rails_app/app/components/component_with_js_mixin/javascripts/extra_one.js +2 -0
  44. data/test/rails_app/app/components/component_with_js_mixin/javascripts/extra_two.js +2 -0
  45. data/test/rails_app/app/components/component_with_js_mixin/javascripts/method_set_one.js +6 -0
  46. data/test/rails_app/app/components/component_with_js_mixin/javascripts/method_set_two.js +5 -0
  47. data/test/rails_app/app/components/component_with_js_mixin.rb +8 -0
  48. data/test/rails_app/app/components/component_with_session_persistence.rb +10 -3
  49. data/test/rails_app/app/components/extended_component_with_js_mixin/javascripts/some_method_set.js +5 -0
  50. data/test/rails_app/app/components/extended_component_with_js_mixin.rb +7 -0
  51. data/test/rails_app/app/components/hello_world_component.rb +31 -0
  52. data/test/rails_app/app/components/server_caller.rb +1 -1
  53. data/test/rails_app/app/components/simple_panel.rb +2 -0
  54. data/test/rails_app/app/components/touch/hello_world_component.rb +25 -0
  55. data/test/rails_app/app/components/touch/server_caller.rb +28 -0
  56. data/test/rails_app/app/components/touch/simple_carousel.rb +17 -0
  57. data/test/rails_app/app/controllers/components_controller.rb +6 -1
  58. data/test/rails_app/app/controllers/touch_controller.rb +6 -0
  59. data/test/rails_app/app/helpers/touch_helper.rb +2 -0
  60. data/test/rails_app/app/views/components/panel_with_autoload.html.erb +2 -0
  61. data/test/rails_app/app/views/components/some_tab_panel.html.erb +11 -0
  62. data/test/rails_app/app/views/layouts/nested.html.erb +5 -0
  63. data/test/rails_app/app/views/layouts/touch.html.erb +13 -0
  64. data/test/rails_app/config/initializers/netzke.rb +1 -1
  65. data/test/rails_app/config/locales/en.yml +7 -1
  66. data/test/rails_app/config/routes.rb +10 -1
  67. data/test/rails_app/db/migrate/20110110132720_create_netzke_component_states.rb +20 -0
  68. data/test/rails_app/db/schema.rb +14 -1
  69. data/test/rails_app/spec/controllers/touch_controller_spec.rb +5 -0
  70. data/test/rails_app/spec/helpers/touch_helper_spec.rb +15 -0
  71. data/test/unit/netzke_core_test.rb +2 -6
  72. metadata +53 -11
  73. data/README.rdoc +0 -136
  74. data/lib/netzke/rails/action_view_ext.rb +0 -103
  75. data/lib/netzke/rails/controller_extensions.rb +0 -31
  76. data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
@@ -0,0 +1,355 @@
1
+ // Because of Netzke's double-underscore notation, Ext.TabPanel should have a different id-delimiter (yes, this should be in netzke-core)
2
+ Ext.TabPanel.prototype.idDelimiter = "___";
3
+
4
+ Ext.QuickTips.init();
5
+
6
+ // We don't want no state managment by default, thank you!
7
+ Ext.state.Provider.prototype.set = function(){};
8
+
9
+ // Check Ext JS version
10
+ (function(){
11
+ var requiredExtVersion = "3.3.1";
12
+ var currentExtVersion = Ext.version;
13
+ if (requiredExtVersion !== currentExtVersion) {
14
+ Netzke.deprecationWarning("Need Ext " + requiredExtVersion + ". You have " + currentExtVersion + ".");
15
+ }
16
+ })();
17
+
18
+ Ext.apply(Netzke.classes.Core.Mixin, {
19
+ height: 400,
20
+ border: false,
21
+
22
+ /*
23
+ Mask shown during loading of a component. Set to false to not mask. Pass config for Ext.LoadMask for configuring msg/cls, etc.
24
+ Set msg to null if mask without any msg is desirable.
25
+ */
26
+ componentLoadMask: true,
27
+
28
+ /* initComponent common for all Netzke components */
29
+ initComponentWithNetzke: function(){
30
+ this.normalizeActions();
31
+
32
+ this.detectActions(this);
33
+
34
+ this.detectComponents(this.items);
35
+
36
+ this.normalizeTools();
37
+
38
+ this.processEndpoints();
39
+
40
+ // This is where the references to different callback functions will be stored
41
+ this.callbackHash = {};
42
+
43
+ // This is where we store the information about components that are currently being loaded with this.loadComponent()
44
+ this.componentsBeingLoaded = {};
45
+
46
+ // Set title
47
+ if (this.mode === "config"){
48
+ if (!this.title) {
49
+ this.title = '[' + this.id + ']';
50
+ } else {
51
+ this.title = this.title + ' [' + this.id + ']';
52
+ }
53
+ } else {
54
+ if (!this.title) {
55
+ this.title = this.id.humanize();
56
+ }
57
+ }
58
+
59
+ // From everywhere accessible FeedbackGhost
60
+ this.feedbackGhost = new Netzke.FeedbackGhost();
61
+
62
+ // Call the original initComponent
63
+ this.initComponentWithoutNetzke();
64
+ },
65
+
66
+ normalizeTools: function() {
67
+ if (this.tools) {
68
+ var normTools = [];
69
+ Ext.each(this.tools, function(tool){
70
+ // Create an event for each action (so that higher-level components could interfere)
71
+ this.addEvents(tool.id+'click');
72
+
73
+ var handler = this.toolActionHandler.createDelegate(this, [tool]);
74
+ normTools.push({id : tool, handler : handler, scope : this});
75
+ }, this);
76
+ this.tools = normTools;
77
+ }
78
+ },
79
+
80
+ /*
81
+ Replaces actions configs with Ext.Action instances, assigning default handler to them
82
+ */
83
+ normalizeActions : function(){
84
+ var normActions = {};
85
+ for (var name in this.actions) {
86
+ // Create an event for each action (so that higher-level components could interfere)
87
+ this.addEvents(name+'click');
88
+
89
+ // Configure the action
90
+ var actionConfig = this.actions[name];
91
+ actionConfig.customHandler = actionConfig.handler;
92
+ actionConfig.handler = this.actionHandler.createDelegate(this); // handler common for all actions
93
+ actionConfig.name = name;
94
+ normActions[name] = new Ext.Action(actionConfig);
95
+ }
96
+ delete(this.actions);
97
+ this.actions = normActions;
98
+ },
99
+
100
+ /*
101
+ Detects action configs in the passed object, and replaces them with instances of Ext.Action created by normalizeActions().
102
+ This detects action in arbitrary level of nesting, which means you can put any other components in your toolbar, and inside of them specify menus/items or even toolbars.
103
+ */
104
+ detectActions: function(o){
105
+ if (Ext.isObject(o)) {
106
+ if ((typeof o.handler === 'string') && Ext.isFunction(this[o.handler.camelize(true)])) {
107
+ // This button config has a handler specified as string - replace it with reference to a real function if it exists
108
+ o.handler = this[o.handler.camelize(true)].createDelegate(this);
109
+ }
110
+ // TODO: this should be configurable!
111
+ Ext.each(["bbar", "tbar", "fbar", "menu", "items", "contextMenu", "buttons"], function(key){
112
+ if (o[key]) {
113
+ var items = [].concat(o[key]); // we need to do it in order to esure that this instance has a separate bbar/tbar/etc, NOT shared via class' prototype
114
+ delete(o[key]);
115
+ o[key] = items;
116
+ this.detectActions(o[key]);
117
+ }
118
+ }, this);
119
+ } else if (Ext.isArray(o)) {
120
+ var a = o;
121
+ Ext.each(a, function(el, i){
122
+ if (Ext.isObject(el)) {
123
+ if (el.action) {
124
+ if (!this.actions[el.action.camelize(true)]) throw "Netzke: action '"+el.action+"' not defined";
125
+ a[i] = this.actions[el.action.camelize(true)];
126
+ delete(el);
127
+ } else {
128
+ this.detectActions(el);
129
+ }
130
+ }
131
+ }, this);
132
+ }
133
+ },
134
+
135
+ /*
136
+ Loads a component. Config options:
137
+ 'name' (required) - the name of the child component to load
138
+ 'container' - the id of a panel with the 'fit' layout where the loaded component will be instantiated
139
+ 'callback' - function that gets called after the component is loaded. It receives the component's instance as parameter.
140
+ 'scope' - scope for the callback.
141
+ */
142
+ loadComponent: function(params){
143
+ if (params.id) {
144
+ params.name = params.id;
145
+ Netzke.deprecationWarning("Using 'id' in loadComponent is deprecated. Use 'name' instead.");
146
+ }
147
+
148
+ params.name = params.name.underscore();
149
+
150
+ // params that will be provided for the server API call (deliver_component); all what's passed in params.params is merged in. This way we exclude from sending along such things as :scope, :callback, etc.
151
+ var serverParams = params.params || {};
152
+ serverParams.name = params.name;
153
+
154
+ // coma-separated list of xtypes of already loaded classes
155
+ serverParams.cache = Netzke.cache.join();
156
+
157
+ var storedConfig = this.componentsBeingLoaded[params.name] = {};
158
+
159
+ // Remember where the loaded component should be inserted into
160
+ if (params.container) {
161
+ storedConfig.container = params.container;
162
+ }
163
+
164
+ // remember the passed callback for the future (per loaded component, as there may be simultaneous ongoing calls)
165
+ if (params.callback) {
166
+ storedConfig.callback = params.callback;
167
+ storedConfig.scope = params.scope;
168
+ // this.callbackHash[params.name.underscore()] = params.callback;
169
+ }
170
+
171
+ var container = params.container && Ext.getCmp(params.container);
172
+ if (container) {
173
+ // remove the old component if the container is specified
174
+ container.removeChild();
175
+ }
176
+
177
+ // Show loading mask if possible
178
+ var containerEl = (container || this).getEl();
179
+ if (this.componentLoadMask && containerEl){
180
+ storedConfig.loadMaskCmp = new Ext.LoadMask(containerEl, this.componentLoadMask);
181
+ storedConfig.loadMaskCmp.show();
182
+ }
183
+
184
+ // do the remote API call
185
+ this.deliverComponent(serverParams);
186
+ },
187
+
188
+ /*
189
+ Called by the server after we ask him to load a component
190
+ */
191
+ componentDelivered : function(config){
192
+ // retrieve the loading config for this component
193
+ var storedConfig = this.componentsBeingLoaded[config.name] || {};
194
+ delete this.componentsBeingLoaded[config.name];
195
+
196
+ if (storedConfig.loadMaskCmp) {
197
+ storedConfig.loadMaskCmp.hide();
198
+ storedConfig.loadMaskCmp.destroy();
199
+ }
200
+
201
+ // instantiate and render it
202
+ var componentInstance = this.instantiateAndRenderComponent(config, storedConfig.container);
203
+
204
+ if (storedConfig.callback) {
205
+ storedConfig.callback.call(storedConfig.scope || this, componentInstance);
206
+ }
207
+
208
+ this.fireEvent('componentload', componentInstance);
209
+ },
210
+
211
+ /*
212
+ Instantiates and renders a component with given config and container
213
+ */
214
+ instantiateAndRenderComponent : function(config, containerId){
215
+ var componentInstance;
216
+ if (containerId) {
217
+ var container = Ext.getCmp(containerId);
218
+ componentInstance = container.instantiateChild(config);
219
+ } else {
220
+ componentInstance = this.instantiateChild(config);
221
+ }
222
+ return componentInstance;
223
+ },
224
+
225
+ /*
226
+ Instantiates and inserts a component into a container with layout 'fit'.
227
+ Arg: an JS object with the following keys:
228
+ - id: id of the receiving container
229
+ - config: configuration of the component to be instantiated and inserted into the container
230
+ */
231
+ // renderComponentInContainer : function(params){
232
+ // var cont = Ext.getCmp(params.container);
233
+ // if (cont) {
234
+ // cont.instantiateChild(params.config);
235
+ // } else {
236
+ // this.instantiateChild(params.config);
237
+ // }
238
+ // },
239
+
240
+ /*
241
+ Returns the parent component
242
+ */
243
+ getParent: function(){
244
+ // simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
245
+ var idSplit = this.id.split("__");
246
+ idSplit.pop();
247
+ var parentId = idSplit.join("__");
248
+
249
+ return parentId === "" ? null : Ext.getCmp(parentId);
250
+ },
251
+
252
+ /*
253
+ Reloads current component (calls the parent to reload it as its component)
254
+ */
255
+ reload : function(){
256
+ var parent = this.getParent();
257
+ if (parent) {
258
+ parent.loadComponent({id:this.localId(parent), container:this.ownerCt.id});
259
+ } else {
260
+ window.location.reload();
261
+ }
262
+ },
263
+
264
+ /*
265
+ Reconfigures the component
266
+ */
267
+ reconfigure: function(config){
268
+ this.ownerCt.instantiateChild(config)
269
+ },
270
+
271
+ // Get the child component
272
+ getChildComponent : function(id){
273
+ if (id === "") {return this};
274
+ id = id.underscore();
275
+ var split = id.split("__");
276
+ if (split[0] === 'parent') {
277
+ split.shift();
278
+ var childInParentScope = split.join("__");
279
+ return this.getParent().getChildComponent(childInParentScope);
280
+ } else {
281
+ return Ext.getCmp(this.id+"__"+id);
282
+ }
283
+ },
284
+
285
+ // At this moment component is fully initializied
286
+ commonAfterConstructor : function(config){
287
+
288
+ // Add the menus
289
+ if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);}
290
+
291
+ // generic events
292
+ this.addEvents(
293
+ 'componentload' // fired when a child is dynamically loaded
294
+ );
295
+
296
+ // Cleaning up on destroy
297
+ this.on('beforedestroy', function(){
298
+ this.cleanUpMenu();
299
+ }, this);
300
+
301
+ this.callbackHash = {};
302
+
303
+ if (this.afterConstructor) this.afterConstructor(config);
304
+ },
305
+
306
+ feedback:function(msg){
307
+ if (this.initialConfig && this.initialConfig.quiet) {
308
+ return false;
309
+ }
310
+
311
+ if (this.feedbackGhost) {
312
+ this.feedbackGhost.showFeedback(msg);
313
+ } else {
314
+ // there's no application to show the feedback - so, we do it ourselves
315
+ if (typeof msg == 'string'){
316
+ alert(msg);
317
+ } else {
318
+ var compoundResponse = "";
319
+ Ext.each(msg, function(m){
320
+ compoundResponse += m.msg + "\n"
321
+ });
322
+ if (compoundResponse != "") {
323
+ alert(compoundResponse);
324
+ }
325
+ }
326
+ }
327
+ },
328
+
329
+ // Common handler for all component's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
330
+ actionHandler : function(comp){
331
+ var actionName = comp.name;
332
+ // If firing corresponding event doesn't return false, call the handler
333
+ if (this.fireEvent(actionName+'click', comp)) {
334
+ var action = this.actions[actionName];
335
+ var customHandler = action.initialConfig.customHandler;
336
+ var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
337
+ if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
338
+
339
+ // call the handler passing it the triggering component
340
+ this[methodName](comp);
341
+ }
342
+ },
343
+
344
+ // Common handler for tools
345
+ toolActionHandler : function(tool){
346
+ // If firing corresponding event doesn't return false, call the handler
347
+ if (this.fireEvent(tool.id+'click')) {
348
+ var methodName = "on"+tool.camelize();
349
+ if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"}
350
+ this[methodName]();
351
+ }
352
+ },
353
+
354
+ onComponentLoad:Ext.emptyFn // gets overridden
355
+ });
@@ -0,0 +1,47 @@
1
+ Ext.ns("Netzke.classes.Core");
2
+ Ext.apply(Netzke.classes.Core.Mixin, {
3
+ /* initComponent common for all Netzke components */
4
+ initComponentWithNetzke: function(){
5
+ this.detectActions(this);
6
+
7
+ this.detectComponents(this.items);
8
+
9
+ this.processEndpoints();
10
+
11
+ // This is where the references to different callback functions will be stored
12
+ this.callbackHash = {};
13
+
14
+ // Call the original initComponent
15
+ this.initComponentWithoutNetzke();
16
+ },
17
+
18
+ /*
19
+ Detects action configs in the passed object, and replaces them with instances of Ext.Action created by normalizeActions().
20
+ This detects action in arbitrary level of nesting, which means you can put any other components in your toolbar, and inside of them specify menus/items or even toolbars.
21
+ */
22
+ detectActions: function(o){
23
+ if (Ext.isObject(o)) {
24
+ if ((typeof o.handler === 'string') && Ext.isFunction(this[o.handler.camelize(true)])) {
25
+ // This button config has a handler specified as string - replace it with reference to a real function if it exists
26
+ o.handler = this[o.handler.camelize(true)];
27
+ o.scope = this;
28
+ }
29
+ // TODO: this should be configurable!
30
+ Ext.each(["items", "dockedItems"], function(key){
31
+ if (o[key]) {
32
+ var items = [].concat(o[key]); // we need to do it in order to esure that this instance has a separate bbar/tbar/etc, NOT shared via class' prototype
33
+ delete(o[key]);
34
+ o[key] = items;
35
+ this.detectActions(o[key]);
36
+ }
37
+ }, this);
38
+ } else if (Ext.isArray(o)) {
39
+ var a = o;
40
+ Ext.each(a, function(el, i){
41
+ if (Ext.isObject(el)) {
42
+ this.detectActions(el);
43
+ }
44
+ }, this);
45
+ }
46
+ }
47
+ });
@@ -1,23 +1,34 @@
1
1
  module Netzke
2
- # Netzke component allows specifying Ext actions.
3
- # An action can be defined in 2 different ways, both of which result in a method definition like this
4
- # def _<some_action>_action
5
- # ...
6
- # end
2
+ # Netzke components allow specifying Ext actions (see http://dev.sencha.com/deploy/dev/docs/?class=Ext.Action)
7
3
  #
4
+ # == Defining actions in a component
8
5
  # The 2 ways to define an action are:
9
- # * as a hash:
10
- # action :bug_server, :text => "Call server", :icon => "/images/icons/phone.png"
6
+ # * as a hash, e.g:
7
+ #
8
+ # action :bug_server, :text => "Call server", :icon => :phone
9
+ #
10
+ # (if the same action was defined in the super class, the superclass's definition get merged with the current definition)
11
11
  #
12
- # * as a block:
12
+ # * as a block, in case you need access to the component's instance, e.g.:
13
13
  # action :bug_server do
14
14
  # {:text => config[:text], :disabled => true}
15
15
  # end
16
16
  #
17
- # The block can optionally receive the configuration of an action being overridden:
18
- # action :bug_server do |orig|
19
- # {:text => config[:text] + orig[:text], :disabled => orig[:disabled]}
17
+ # Both of the ways result in a definition of an instance method named {action_name}_action. So, overriding an action in the child class is done be redefining the method, e.g.:
18
+ #
19
+ # def bug_server_action
20
+ # # super will have the superclass's action definition
20
21
  # end
22
+ #
23
+ # == I18n of actions
24
+ # The text and tooltip for an action will be automatically picked up from a locale file when possible.
25
+ # E.g., an action named "some_action" and defined in the component +MyComponents::CoolComponent+, will look for its text in:
26
+ #
27
+ # I18n.t('my_components.cool_component.actions.some_action')
28
+ #
29
+ # and for its tooltip in:
30
+ #
31
+ # I18n.t('my_components.cool_component.actions.some_action_tooltip')
21
32
  module Actions
22
33
  extend ActiveSupport::Concern
23
34
 
@@ -64,7 +75,7 @@ module Netzke
64
75
 
65
76
  # All actions for this instance
66
77
  def actions
67
- @actions ||= self.class.registered_actions.inject({}){ |res, name| res.merge(name.to_sym => normalize_action_config(send(ACTION_METHOD_NAME % name))) }
78
+ @actions ||= self.class.registered_actions.inject({}){ |res, name| res.merge(name.to_sym => normalize_action_config(send(ACTION_METHOD_NAME % name).merge(:name => name.to_s))) }
68
79
  end
69
80
 
70
81
  def js_config_with_actions #:nodoc
@@ -72,40 +83,22 @@ module Netzke
72
83
  end
73
84
 
74
85
  private
86
+
75
87
  def normalize_action_config(config)
76
88
  if config[:icon].is_a?(Symbol)
77
- config[:icon] = Netzke::Core.with_icons ? Netzke::Core.icons_uri + "/" + config[:icon].to_s + ".png" : nil
89
+ config[:icon] = uri_to_icon(config[:icon])
78
90
  end
79
91
 
80
- config[:text] ||= config[:name].humanize
92
+ # Default text and tooltip
93
+ default_text = I18n.t(i18n_id + ".actions." + config[:name], :default => config[:name].humanize)
94
+ config[:text] ||= default_text
95
+ config[:tooltip] ||= I18n.t(i18n_id + ".actions." + config[:name] + "_tooltip", :default => default_text)
81
96
 
82
97
  config
83
98
  end
84
99
 
85
- def auto_collect_actions_from_config_and_js_properties
86
- # res = extract_actions(js_properties)
87
- # puts %Q(!!! res: #{res.inspect}\n)
100
+ def uri_to_icon(icon)
101
+ Netzke::Core.with_icons ? Netzke::Core.icons_uri + "/" + icon.to_s + ".png" : nil
88
102
  end
89
-
90
- # def extract_actions(hsh)
91
- # hsh.each_pair.inject({}) do |r,(k,v)|
92
- # v.is_a?(Array) ? r.merge(extract_actions_from_array(v)) : r
93
- # end
94
- # end
95
- #
96
- # def extract_actions_from_array(arry)
97
- # arry.inject({}) do |r, el|
98
- # if el.is_a?(Hash)
99
- # el[:action] ? r.merge(el[:action] => default_action_config(el[:action])) : r.merge(extract_actions(el))
100
- # else
101
- # r
102
- # end
103
- # end
104
- # end
105
- #
106
- # def default_action_config(action_name)
107
- # {:text => action_name.to_s.humanize}
108
- # end
109
-
110
103
  end
111
104
  end