netzke-core 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,17 @@
1
+ = edge
2
+ * API change: Netzke::Base: <tt>id_name</tt> accessor renamed to <tt>global_id</tt>
3
+ * Code: several internal code changes
4
+ * Code: lightly better test coverage
5
+ * New: <tt>Netzke::Base#global_id_by_reference</tt> method
6
+ * Compatibility: resolving conflicts with the <tt>api</tt> property in some Ext v3.0 components
7
+ * Fix: <tt>load_aggregatee_with_cache</tt> was throwing exception when the requested aggregatee wasn't defined
8
+ * New: <tt>persistent_config_id</tt> configuration option allows specifying an id by which persistent configuration is identified for the widget. Handy if different homogeneous widgets need to share the same persistent configuration.
9
+ * New: <tt>Netzke::Base#persistent_config</tt> method now accepts an optional boolean parameter signalizing that the configuration is global (not bound to a widget)
10
+ * Impr: cleaner handling of actions and toolbars; fbar configuration introduced.
11
+ * Impr: calling an API method now provides for the result value (if return by the server) in the callback.
12
+ * Impr: allows name spaced creation of Netzke widgets, e.g. widgets can now be defined under any module under Netzke, not only *directly* under Netzke.
13
+ * New: support for Ext.Window-based widgets (it'll call show() on them when the "*_widget_render" helper is used).
14
+
1
15
  = v0.4.4 - 2009-10-12
2
16
  * API change: default handlers for actions and tools are now supposed to be prefixed with "on". E.g.: if you declare an action named <tt>clear_table</tt>, the handler must be called (in Ruby) <tt>on_clear_table</tt> (mapped to <tt>onClearTable</tt> in JavaScript).
3
17
  * Internal: the JavaScript instance now knows if persistent config is enabled (by checking this.persistentConfig).
data/README.rdoc CHANGED
@@ -6,10 +6,15 @@ This is the bare bones of the Netzke framework. Use it to build your own widgets
6
6
  The idea behind the Netzke framework is that it allows you write reusable client/server code. Create a widget, and then embed it (or load it dynamically) into your Ext-based applications or HTML pages. For more info, see the links below.
7
7
 
8
8
  == Instalation
9
-
9
+ The gem is hosted on gemcutter. If you haven't yet enabled gemcutter, run the following:
10
+
11
+ sudo gem install gemcutter && gem tumble
12
+
13
+ Install the gem
14
+
10
15
  sudo gem install netzke-core
11
16
 
12
- If you want the most recent changes, install this as a plugin:
17
+ If you want to experiment with the most recent changes, install this as a plugin:
13
18
 
14
19
  script/plugin install git://github.com/skozlov/netzke-core.git
15
20
 
data/Rakefile CHANGED
@@ -1,20 +1,22 @@
1
1
  begin
2
2
  require 'jeweler'
3
3
  Jeweler::Tasks.new do |gemspec|
4
+ gemspec.version = "0.4.5"
4
5
  gemspec.name = "netzke-core"
5
6
  gemspec.summary = "Build ExtJS/Rails widgets with minimum effort"
6
- gemspec.description = "Build ExtJS/Rails widgets with minimum effort"
7
+ gemspec.description = "Allows building ExtJS/Rails reusable code in a DRY way"
7
8
  gemspec.email = "sergei@playcode.nl"
8
9
  gemspec.homepage = "http://github.com/skozlov/netzke-core"
9
10
  gemspec.rubyforge_project = "netzke-core"
10
11
  gemspec.authors = ["Sergei Kozlov"]
11
12
  end
12
- Jeweler::RubyforgeTasks.new do |rubyforge|
13
- rubyforge.doc_task = "rdoc"
14
- end
13
+ Jeweler::GemcutterTasks.new
14
+ # Jeweler::RubyforgeTasks.new do |rubyforge|
15
+ # rubyforge.doc_task = "rdoc"
16
+ # end
15
17
 
16
18
  rescue LoadError
17
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
20
  end
19
21
 
20
22
  require 'rake/rdoctask'
data/javascripts/core.js CHANGED
@@ -4,8 +4,7 @@ This file gets loaded along with the rest of Ext library at the initial load
4
4
 
5
5
  Ext.BLANK_IMAGE_URL = "/extjs/resources/images/default/s.gif";
6
6
  Ext.namespace('Ext.netzke'); // namespace for extensions that depend on Ext
7
- Ext.namespace('Netzke'); // namespace for extensions that do not depend on Ext
8
- Ext.netzke.cache = {};
7
+ Ext.namespace('Netzke.classes'); // namespace for extensions that do not depend on Ext
9
8
 
10
9
  Ext.QuickTips.init(); // seems obligatory in Ext v2.2.1, otherwise Ext.Component#destroy() stops working properly
11
10
 
@@ -88,18 +87,19 @@ Ext.widgetMixIn = {
88
87
  height: 400,
89
88
  // width: 800,
90
89
  border: false,
91
- is_netzke: true, // to distinguish Netzke components from regular Ext components
90
+ isNetzke: true, // to distinguish Netzke components from regular Ext components
91
+ latestResult: {}, // latest result returned from the server via an API call
92
92
 
93
93
  /*
94
94
  Loads aggregatee into a container.
95
95
  */
96
96
  loadAggregatee: function(params){
97
- // params that will be provided for the server API call (load_aggregatee_with_cache); all what's passed in params.params is merged in
97
+ // params that will be provided for the server API call (load_aggregatee_with_cache); all what's passed in params.params is merged in. This way we exclude from sending along such things as :scope, :callback, etc.
98
98
  var apiParams = Ext.apply({id: params.id, container: params.container}, params.params);
99
99
 
100
100
  // build the cached widgets list to send it to the server
101
101
  var cachedWidgetNames = [];
102
- for (name in Ext.netzke.cache) {
102
+ for (name in Netzke.classes) {
103
103
  cachedWidgetNames.push(name);
104
104
  }
105
105
  apiParams.cache = Ext.encode(cachedWidgetNames);
@@ -112,7 +112,7 @@ Ext.widgetMixIn = {
112
112
  // visually disable the container while the widget is being loaded
113
113
  // Ext.getCmp(params.container).disable();
114
114
 
115
- Ext.getCmp(params.container).removeChild(); // remove the old widget
115
+ if (params.container) Ext.getCmp(params.container).removeChild(); // remove the old widget if the container is specified
116
116
 
117
117
  // do the remote API call
118
118
  this.loadAggregateeWithCache(apiParams);
@@ -177,7 +177,11 @@ Ext.widgetMixIn = {
177
177
  */
178
178
  renderWidgetInContainer : function(params){
179
179
  var cont = Ext.getCmp(params.container);
180
- cont.instantiateChild(params.config);
180
+ if (cont) {
181
+ cont.instantiateChild(params.config);
182
+ } else {
183
+ this.instantiateChild(params.config);
184
+ }
181
185
  },
182
186
 
183
187
  /*
@@ -261,13 +265,18 @@ Ext.widgetMixIn = {
261
265
  }
262
266
  },
263
267
 
264
- // Common handler for actions
265
- actionHandler : function(action){
268
+ // Common handler for all widget's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
269
+ actionHandler : function(comp){
270
+ var actionName = comp.name;
266
271
  // If firing corresponding event doesn't return false, call the handler
267
- if (this.fireEvent(action.name+'click', action)) {
268
- var methodName = action.fn || "on"+action.name.camelize();
269
- if (!this[methodName]) {throw "Netzke: handler for action '" + action.name + "' is undefined"}
270
- this[methodName](action);
272
+ if (this.fireEvent(actionName+'click', comp)) {
273
+ var action = this.actions[actionName];
274
+ var customHandler = action.initialConfig.customHandler;
275
+ var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
276
+ if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
277
+
278
+ // call the handler passing it the triggering component
279
+ this[methodName](comp);
271
280
  }
272
281
  },
273
282
 
@@ -292,10 +301,10 @@ Ext.widgetMixIn = {
292
301
  // execute commands from server
293
302
  this.bulkExecute(Ext.decode(response.responseText));
294
303
 
295
- // provade callback if needed
304
+ // provide callback if needed
296
305
  if (typeof callback == 'function') {
297
306
  if (!scope) scope = this;
298
- callback.apply(scope);
307
+ callback.apply(scope, [this.latestResult]);
299
308
  }
300
309
  }
301
310
  },
@@ -303,9 +312,15 @@ Ext.widgetMixIn = {
303
312
  });
304
313
  },
305
314
 
306
- /* Parse the bbar and tbar (both Arrays), replacing the strings with the corresponding methods. For example:
307
- replaceStringsWithActions( ['add', {text:'Menu', menu:['edit', 'delete']}] )
308
- => [scope.actions['add'], {text:'Menu', menu:[scope.actions['edit'], scope.actions['delete']]}]
315
+ setResult: function(result) {
316
+ this.latestResult = result;
317
+ },
318
+
319
+ /* Normalize an array of abstracted button configs into an array of Ext button configs according to the following rules:
320
+ - if the element is a string and <tt>scope</tt> has an action with this name, replace this element with that action; if <tt>scope</tt> has no corresponding action, don't do anything (Ext will take care of it - display as text, or a separator, etc)
321
+ - if the element is an object, then:
322
+ -- if this object has a <tt>menu</tt> property - it's a nested menu; the value is expected to be an array of abstracted button configs, so, proceed recursively.
323
+ -- if this object has a <tt>handler</tt> property and the value correspond to a function in <tt>scope</tt> - replace this value with the reference to that function
309
324
  */
310
325
  normalizeMenuItems: function(arry, scope){
311
326
  var res = []; // new array
@@ -315,58 +330,64 @@ Ext.widgetMixIn = {
315
330
  if (scope.actions[camelized]){
316
331
  res.push(scope.actions[camelized]);
317
332
  } else {
318
- // if there's no action with this name, maybe it's a separator or something
333
+ // if there's no action with this name, maybe it's a separator or text or whatever
319
334
  res.push(o);
320
335
  }
321
336
  } else if (Netzke.isObject(o)) {
322
337
  // look inside the objects...
323
- for (var key in o) {
324
- if (Ext.isArray(o[key])) {
325
- // ... and recursively process inner arrays found
326
- o[key] = this.normalizeMenuItems(o[key], scope);
327
- }
338
+ if (o.menu) {
339
+ // ... and recursively process nested menus
340
+ o.menu = this.normalizeMenuItems(o.menu, scope);
341
+ } else if (o.handler && Ext.isFunction(scope[o.handler.camelize(true)])) {
342
+ // This button config has a handler specified as string - replace it with reference to a real function if it exists
343
+ o.handler = scope[o.handler.camelize(true)];
328
344
  }
329
345
  res.push(o);
330
346
  }
331
347
  }, this);
348
+
349
+ delete arry;
350
+
332
351
  return res;
333
352
  },
334
353
 
335
354
 
336
- // Every Netzke widget
355
+ // Code run before calling Ext's constructor - normalizing config to provide Netzke additional functionality
337
356
  commonBeforeConstructor : function(config){
338
- this.actions = {};
339
-
340
- // Generate methods for api points
341
- if (!config.api) { config.api = []; }
342
- config.api.push('load_aggregatee_with_cache'); // all netzke widgets get this API
343
- Ext.each(config.api, function(intp){
357
+ // Dynamically create methods for api points, so that we could later call them like: this.myApiMethod()
358
+ var apiPoints = config.netzkeApi || [];
359
+ apiPoints.push('load_aggregatee_with_cache'); // all netzke widgets get this API point
360
+ Ext.each(apiPoints, function(intp){
344
361
  this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
345
362
  }, this);
346
363
 
347
- // Create Ext.Actions based on config.actions
364
+ // This will contain Ext.Action instances
365
+ this.actions = {};
366
+
367
+ // Create Ext.Action instances based on config.actions
348
368
  if (config.actions) {
349
- this.testActions = {};
350
369
  for (var name in config.actions) {
351
370
  // Create an event for each action (so that higher-level widgets could interfere)
352
371
  this.addEvents(name+'click');
353
372
 
354
373
  // Configure the action
355
374
  var actionConfig = config.actions[name];
356
- actionConfig.handler = this.actionHandler.createDelegate(this);
375
+ actionConfig.customHandler = actionConfig.handler || actionConfig.fn; //DEPRECATED: .fn is kept for backward compatibility, preferred way is to specify handler
376
+ actionConfig.handler = this.actionHandler.createDelegate(this); // ! this is the "wrapper-handler", which is common for all actions!
357
377
  actionConfig.name = name;
358
378
  this.actions[name] = new Ext.Action(actionConfig);
359
379
  }
360
-
361
- config.bbar = config.bbar && this.normalizeMenuItems(config.bbar, this);
362
- config.tbar = config.tbar && this.normalizeMenuItems(config.tbar, this);
363
- config.menu = config.menu && this.normalizeMenuItems(config.menu, this);
364
- config.contextMenu = config.contextMenu && this.normalizeMenuItems(config.contextMenu, this);
365
380
 
366
381
  // TODO: need to rethink this action related stuff
367
382
  config.actions = this.actions;
368
-
369
383
  }
384
+
385
+ config.bbar = config.bbar && this.normalizeMenuItems(config.bbar, this);
386
+ config.tbar = config.tbar && this.normalizeMenuItems(config.tbar, this);
387
+ config.fbar = config.fbar && this.normalizeMenuItems(config.fbar, this);
388
+ config.contextMenu = config.contextMenu && this.normalizeMenuItems(config.contextMenu, this);
389
+
390
+ config.menu = config.menu && this.normalizeMenuItems(config.menu, this);
370
391
 
371
392
  // Normalize tools
372
393
  if (config.tools) {
@@ -382,7 +403,13 @@ Ext.widgetMixIn = {
382
403
  }
383
404
 
384
405
  // Set title
385
- if (!config.title) config.title = config.id.humanize();
406
+ if (!config.title) {
407
+ config.title = config.id.humanize();
408
+ } else {
409
+ if (config.mode === "config") {
410
+ config.title = config.title + ' (' + config.id + ')';
411
+ }
412
+ }
386
413
  },
387
414
 
388
415
  // At this moment component is fully initializied
@@ -486,17 +513,30 @@ Ext.override(Ext.Container, {
486
513
  return this.items ? this.items.get(0) : null; // need this check in case when the container is not yet rendered, like an inactive tab in the TabPanel
487
514
  },
488
515
 
516
+ // Remove the child
489
517
  removeChild : function(){
490
518
  this.remove(this.getWidget());
491
519
  },
492
520
 
493
- instantiateChild : function(config){
494
- this.remove(this.getWidget()); // first delete previous widget
495
-
496
- if (!config) return false; // simply remove current widget if null is passed
521
+ // Given a scoped class name, returns the actual class, e.g.: "Netzke.GridPanel" => Netzke.classes.Netzke.GridPanel
522
+ classifyScopedName : function(n){
523
+ var klass = Netzke.classes;
524
+ Ext.each(n.split("."), function(s){
525
+ klass = klass[s];
526
+ });
527
+ return klass;
528
+ },
497
529
 
498
- var instance = new Ext.netzke.cache[config.widgetClassName](config);
499
- this.add(instance);
500
- this.doLayout();
530
+ // Instantiates an aggregatee by its config. If it appears to be a window, shows it instead of adding as item.
531
+ instantiateChild : function(config){
532
+ var klass = this.classifyScopedName(config.scopedClassName);
533
+ var instance = new klass(config);
534
+ if (instance.isXType("netzkewindow")) {
535
+ instance.show();
536
+ } else {
537
+ this.remove(this.getWidget()); // first delete previous widget
538
+ this.add(instance);
539
+ this.doLayout();
540
+ }
501
541
  }
502
542
  });
data/lib/netzke/base.rb CHANGED
@@ -38,138 +38,129 @@ module Netzke
38
38
  # netzke :form_panel,
39
39
  # :data_class_name => "User" # FormPanel specific option
40
40
  class Base
41
+ extend ActiveSupport::Memoizable
42
+
41
43
  include Netzke::BaseJs # javascript (client-side)
42
44
 
43
- module ClassMethods
44
- # Class-level Netzke::Base configuration. The defaults also get specified here.
45
- def config
46
- set_default_config({
47
- # which javascripts and stylesheets must get included at the initial load (see netzke-core.rb)
48
- :javascripts => [],
49
- :stylesheets => [],
50
-
51
- :persistent_config_manager => "NetzkePreference",
52
- :ext_location => defined?(RAILS_ROOT) && "#{RAILS_ROOT}/public/extjs",
53
- :default_config => {
54
- :persistent_config => true
55
- }
56
- })
57
- end
58
-
59
- def configure(*args)
60
- if args.first.is_a?(Symbol)
61
- # first arg is a Symbol
62
- config[args.first] = args.last
63
- else
64
- config.deep_merge!(args.first)
65
- end
66
-
67
- enforce_config_consistency
45
+ attr_accessor :parent, :name, :global_id, :permissions, :session
46
+
47
+ # Class-level Netzke::Base configuration. The defaults also get specified here.
48
+ def self.config
49
+ set_default_config({
50
+ # which javascripts and stylesheets must get included at the initial load (see netzke-core.rb)
51
+ :javascripts => [],
52
+ :stylesheets => [],
53
+
54
+ :persistent_config_manager => "NetzkePreference",
55
+ :ext_location => defined?(RAILS_ROOT) && "#{RAILS_ROOT}/public/extjs",
56
+ :default_config => {
57
+ :persistent_config => true
58
+ }
59
+ })
60
+ end
61
+
62
+ def self.set_default_config(c) #:nodoc:
63
+ @@config ||= {}
64
+ @@config[self.name] ||= c
65
+ end
66
+
67
+ # Override class-level defaults specified in <tt>Netzke::Base.config</tt>.
68
+ # E.g. in config/initializers/netzke-config.rb:
69
+ #
70
+ # Netzke::GridPanel.configure :default_config => {:persistent_config => true}
71
+ def self.configure(*args)
72
+ if args.first.is_a?(Symbol)
73
+ config[args.first] = args.last
74
+ else
75
+ # first arg is hash
76
+ config.deep_merge!(args.first)
68
77
  end
69
78
 
70
- def enforce_config_consistency; end
71
-
72
- # "Netzke::SomeWidget" => "SomeWidget"
73
- def short_widget_class_name
74
- self.name.split("::").last
75
- end
76
-
77
- # Multi-user support (deprecated in favor of controller sessions)
78
- def user
79
- @@user ||= nil
80
- end
81
-
82
- def user=(user)
83
- @@user = user
84
- end
85
-
86
- # Access to controller sessions
87
- def session
88
- @@session ||= {}
89
- end
79
+ # widget may implement some kind of control for configuration consistency
80
+ enforce_config_consistency if respond_to?(:enforce_config_consistency)
81
+ end
82
+
83
+ # Short widget class name, e.g.:
84
+ # Netzke::Module::SomeWidget => Module::SomeWidget
85
+ def self.short_widget_class_name
86
+ self.name.sub("Netzke::", "")
87
+ end
90
88
 
91
- def session=(s)
92
- @@session = s
93
- end
89
+ # Access to controller sessions
90
+ def self.session
91
+ @@session ||= {}
92
+ end
94
93
 
95
- # called by controller at the moment of successfull login
96
- def login
97
- session[:_netzke_next_request_is_first_after_login] = true
98
- end
99
-
100
- # called by controller at the moment of logout
101
- def logout
102
- session[:_netzke_next_request_is_first_after_logout] = true
103
- end
94
+ def self.session=(s)
95
+ @@session = s
96
+ end
104
97
 
105
- # Use this class method to declare connection points between client side of a widget and its server side.
106
- # A method in a widget class with the same name will be (magically) called by the client side of the widget.
107
- # See netzke-basepack's GridPanel for an example.
108
- def api(*api_points)
109
- apip = read_inheritable_attribute(:api_points) || []
110
- api_points.each{|p| apip << p}
111
- write_inheritable_attribute(:api_points, apip)
112
-
113
- # It may be needed later for security
114
- api_points.each do |apip|
115
- module_eval <<-END, __FILE__, __LINE__
116
- def api_#{apip}(*args)
117
- #{apip}(*args).to_nifty_json
118
- end
119
- # FIXME: commented out because otherwise ColumnOperations stop working
120
- # def #{apip}(*args)
121
- # flash :warning => "API point '#{apip}' is not implemented for widget '#{short_widget_class_name}'"
122
- # {:flash => @flash}
123
- # end
124
- END
98
+ # Should be called by session controller at the moment of successfull login
99
+ def self.login
100
+ session[:_netzke_next_request_is_first_after_login] = true
101
+ end
102
+
103
+ # Should be called by session controller at the moment of logout
104
+ def self.logout
105
+ session[:_netzke_next_request_is_first_after_logout] = true
106
+ end
107
+
108
+ # Declare connection points between client side of a widget and its server side. For example:
109
+ #
110
+ # api :reset_data
111
+ #
112
+ # will provide JavaScript side with a method <tt>resetData</tt> that will result in a call to Ruby
113
+ # method <tt>reset_data</tt>, e.g.:
114
+ #
115
+ # this.resetData({hard:true});
116
+ #
117
+ # See netzke-basepack's GridPanel for an example.
118
+ def self.api(*api_points)
119
+ apip = read_inheritable_attribute(:api_points) || []
120
+ api_points.each{|p| apip << p}
121
+ write_inheritable_attribute(:api_points, apip)
122
+
123
+ # It may be needed later for security
124
+ api_points.each do |apip|
125
+ module_eval <<-END, __FILE__, __LINE__
126
+ def api_#{apip}(*args)
127
+ #{apip}(*args).to_nifty_json
125
128
  end
129
+ # FIXME: commented out because otherwise ColumnOperations stop working
130
+ # def #{apip}(*args)
131
+ # flash :warning => "API point '#{apip}' is not implemented for widget '#{short_widget_class_name}'"
132
+ # {:flash => @flash}
133
+ # end
134
+ END
126
135
  end
136
+ end
127
137
 
128
- def api_points
129
- read_inheritable_attribute(:api_points)
130
- end
131
-
132
- # returns an instance of a widget defined in the config
133
- def instance_by_config(config)
134
- widget_class = "Netzke::#{config[:widget_class_name]}".constantize
135
- widget_class.new(config)
136
- end
137
-
138
- # persistent_config and layout manager classes
139
- def persistent_config_manager_class
140
- Netzke::Base.config[:persistent_config_manager].try(:constantize)
141
- rescue NameError
142
- nil
143
- end
138
+ api :load_aggregatee_with_cache # every widget gets this api
144
139
 
145
- # Return persistent config class
146
- def persistent_config
147
- # if the class is not present, fake it (it will not store anything, and always return nil)
148
- if persistent_config_manager_class.nil?
149
- {}
150
- else
151
- persistent_config_manager_class
152
- end
153
- end
154
-
155
- private
156
- def set_default_config(c)
157
- @@config ||= {}
158
- @@config[self.name] ||= c
159
- end
160
-
140
+ # Array of API-points specified with <tt>Netzke::Base.api</tt> method
141
+ def self.api_points
142
+ read_inheritable_attribute(:api_points)
161
143
  end
162
- extend ClassMethods
163
144
 
164
- # If the widget has persistent config in its disposal
165
- def persistent_config_enabled?
166
- !persistent_config_manager_class.nil? && config[:persistent_config]
145
+ # Instance of widget by config
146
+ def self.instance_by_config(config)
147
+ widget_class = "Netzke::#{config[:widget_class_name]}".constantize
148
+ widget_class.new(config)
167
149
  end
168
150
 
169
- attr_accessor :parent, :name, :id_name, :permissions, :session
170
-
171
- api :load_aggregatee_with_cache # every widget gets this api
151
+ # Persistent config manager class
152
+ def self.persistent_config_manager_class
153
+ Netzke::Base.config[:persistent_config_manager].try(:constantize)
154
+ rescue NameError
155
+ nil
156
+ end
172
157
 
158
+ # Return persistent config class
159
+ # def self.persistent_config
160
+ # # if the class is not present, fake it (it will not store anything, and always return nil)
161
+ # persistent_config_manager_class || {}
162
+ # end
163
+
173
164
  # Widget initialization process
174
165
  # * the config hash is available to the widget after the "super" call in the initializer
175
166
  # * override/add new default configuration options into the "default_config" method
@@ -179,49 +170,78 @@ module Netzke
179
170
  @passed_config = config # configuration passed at the moment of instantiation
180
171
  @parent = parent
181
172
  @name = config[:name].nil? ? short_widget_class_name.underscore : config[:name].to_s
182
- @id_name = parent.nil? ? @name : "#{parent.id_name}__#{@name}"
173
+ @global_id = parent.nil? ? @name : "#{parent.global_id}__#{@name}"
183
174
  @flash = []
184
175
  end
185
176
 
186
- # add flatten method to Hash
187
- Hash.class_eval do
188
- def flatten(preffix = "")
189
- res = []
190
- self.each_pair do |k,v|
191
- if v.is_a?(Hash)
192
- res += v.flatten(k)
193
- else
194
- res << {
195
- :name => ((preffix.to_s.empty? ? "" : preffix.to_s + "__") + k.to_s).to_sym,
196
- :value => v,
197
- :type => (["TrueClass", "FalseClass"].include?(v.class.name) ? 'Boolean' : v.class.name).to_sym
198
- }
199
- end
200
- end
201
- res
202
- end
203
- end
177
+ #
178
+ # Configuration
179
+ #
204
180
 
181
+ # Default config - before applying any passed configuration
205
182
  def default_config
206
- self.class.config[:default_config].nil? ? {} : {}.merge!(self.class.config[:default_config])
183
+ self.class.config[:default_config].nil? ? {} : {}.merge(self.class.config[:default_config])
184
+ end
185
+
186
+ # Static, hardcoded config. Consists of default values merged with config that was passed during instantiation
187
+ def initial_config
188
+ default_config.deep_merge(@passed_config)
189
+ end
190
+ memoize :initial_config
191
+
192
+ # Config that is not overwritten by parents and sessions
193
+ def independent_config
194
+ initial_config.deep_merge(persistent_config_hash)
207
195
  end
196
+ memoize :independent_config
208
197
 
209
- # Access to the config that takes into account all possible ways to configure a widget. *Read only*.
198
+ # If the widget has persistent config in its disposal
199
+ def persistent_config_enabled?
200
+ !persistent_config_manager_class.nil? && initial_config[:persistent_config]
201
+ end
202
+
203
+ # Store some setting in the database as if it was a hash, e.g.:
204
+ # persistent_config["window.size"] = 100
205
+ # persistent_config["window.size"] => 100
206
+ # This method is user-aware
207
+ def persistent_config(global = false)
208
+ if persistent_config_enabled? || global
209
+ config_class = self.class.persistent_config_manager_class
210
+ config_class.widget_name = global ? nil : persistent_config_id # pass to the config class our unique name
211
+ config_class
212
+ else
213
+ # if we can't use presistent config, all the calls to it will always return nil,
214
+ # and the "="-operation will be ignored
215
+ logger.debug "==> NETZKE: no persistent config is set up for widget '#{global_id}'"
216
+ {}
217
+ end
218
+ end
219
+
220
+ # A string which will identify the persistent config records for this widget
221
+ def persistent_config_id #:nodoc:
222
+ initial_config[:persistent_config_id] || global_id
223
+ end
224
+
225
+ def update_persistent_ext_config(hsh)
226
+ current_config = persistent_config[:ext_config] || {}
227
+ current_config.deep_merge!(hsh.convert_keys{ |k| k.to_s }) # first, recursively stringify the keys
228
+ persistent_config[:ext_config] = current_config
229
+ end
230
+
231
+ # Resulting config that takes into account all possible ways to configure a widget. *Read only*.
232
+ # Translates into something like this:
233
+ # default_config.
234
+ # deep_merge(@passed_config).
235
+ # deep_merge(persistent_config_hash).
236
+ # deep_merge(strong_parent_config).
237
+ # deep_merge(strong_session_config)
210
238
  def config
211
- # Translates into something like this:
212
- # @config ||= default_config.
213
- # deep_merge(@passed_config).
214
- # deep_merge(persistent_config_hash).
215
- # deep_merge(strong_parent_config).
216
- # deep_merge(strong_session_config)
217
- @config ||= independent_config.
218
- deep_merge(strong_parent_config).
219
- deep_merge(strong_session_config)
220
-
239
+ independent_config.deep_merge(strong_parent_config).deep_merge(strong_session_config)
221
240
  end
241
+ memoize :config
222
242
 
223
243
  def flat_config(key = nil)
224
- fc = config.flatten
244
+ fc = config.flatten_with_type
225
245
  key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
226
246
  end
227
247
 
@@ -229,35 +249,33 @@ module Netzke
229
249
  @strong_parent_config ||= parent.nil? ? {} : parent.strong_children_config
230
250
  end
231
251
 
232
- # Config that is not overwritten by parents and sessions
233
- def independent_config
234
- @independent_config ||= initial_config.deep_merge(persistent_config_hash)
235
- end
236
-
237
252
  def flat_independent_config(key = nil)
238
- fc = independent_config.flatten
253
+ fc = independent_config.flatten_with_type
239
254
  key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
240
255
  end
241
256
 
242
257
  def flat_default_config(key = nil)
243
- fc = default_config.flatten
258
+ fc = default_config.flatten_with_type
244
259
  key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
245
260
  end
246
261
 
247
- # Static, hardcoded config. Consists of default values merged with config that was passed during instantiation
248
- def initial_config
249
- @initial_config ||= default_config.deep_merge(@passed_config)
250
- end
251
-
252
262
  def flat_initial_config(key = nil)
253
- fc = initial_config.flatten
263
+ fc = initial_config.flatten_with_type
254
264
  key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
255
265
  end
256
266
 
257
- def build_persistent_config_hash
267
+ # Returns a hash built from all persistent config values for the current widget, following the double underscore
268
+ # naming convention. E.g., if we have the following persistent config pairs:
269
+ # enabled => true
270
+ # layout__width => 100
271
+ # layout__header__height => 20
272
+ #
273
+ # this method will return the following hash:
274
+ # {:enabled => true, :layout => {:width => 100, :header => {:height => 20}}}
275
+ def persistent_config_hash
258
276
  return {} if !initial_config[:persistent_config]
259
277
 
260
- prefs = NetzkePreference.find_all_for_widget(id_name)
278
+ prefs = NetzkePreference.find_all_for_widget(persistent_config_id)
261
279
  res = {}
262
280
  prefs.each do |p|
263
281
  hsh_levels = p.name.split("__").map(&:to_sym)
@@ -273,13 +291,10 @@ module Netzke
273
291
  # So we need to recursively merge it into the final result
274
292
  res.deep_merge!(hsh_levels.first => anchor)
275
293
  end
276
- res
277
- end
278
-
279
- def persistent_config_hash
280
- @persistent_config_hash ||= build_persistent_config_hash
294
+ res.convert_keys{ |k| k.to_sym } # recursively symbolize the keys
281
295
  end
282
-
296
+ memoize :persistent_config_hash
297
+
283
298
  def ext_config
284
299
  config[:ext_config] || {}
285
300
  end
@@ -313,7 +328,7 @@ module Netzke
313
328
  end
314
329
 
315
330
  def widget_session
316
- session[id_name] ||= {}
331
+ session[global_id] ||= {}
317
332
  end
318
333
 
319
334
  # Rails' logger
@@ -330,22 +345,6 @@ module Netzke
330
345
  res.uniq
331
346
  end
332
347
 
333
- # Store some setting in the database as if it was a hash, e.g.:
334
- # persistent_config["window.size"] = 100
335
- # persistent_config["window.size"] => 100
336
- # This method is user-aware
337
- def persistent_config
338
- if config[:persistent_config]
339
- config_class = self.class.persistent_config
340
- config_class.widget_name = id_name # pass to the config class our unique name
341
- config_class
342
- else
343
- # if we can't use presistent config, all the calls to it will always return nil, and the "="-operation will be ignored
344
- logger.debug "==> NETZKE: no persistent config is set up for widget '#{id_name}'"
345
- {}
346
- end
347
- end
348
-
349
348
  # 'Netzke::Grid' => 'Grid'
350
349
  def short_widget_class_name
351
350
  self.class.short_widget_class_name
@@ -383,7 +382,7 @@ module Netzke
383
382
 
384
383
  def remove_aggregatee(aggr)
385
384
  if config[:persistent_config]
386
- persistent_config_manager_class.delete_all_for_widget("#{id_name}__#{aggr}")
385
+ persistent_config_manager_class.delete_all_for_widget("#{global_id}__#{aggr}")
387
386
  end
388
387
  aggregatees[aggr] = nil
389
388
  end
@@ -403,9 +402,9 @@ module Netzke
403
402
  name.to_s.split('__').each do |aggr|
404
403
  aggr = aggr.to_sym
405
404
  aggregatee_config = aggregator.aggregatees[aggr]
406
- raise ArgumentError, "No aggregatee '#{aggr}' defined for widget '#{aggregator.id_name}'" if aggregatee_config.nil?
405
+ raise ArgumentError, "No aggregatee '#{aggr}' defined for widget '#{aggregator.global_id}'" if aggregatee_config.nil?
407
406
  short_class_name = aggregatee_config[:widget_class_name]
408
- raise ArgumentError, "No widget_class_name specified for aggregatee #{aggr} of #{aggregator.id_name}" if short_class_name.nil?
407
+ raise ArgumentError, "No widget_class_name specified for aggregatee #{aggr} of #{aggregator.global_id}" if short_class_name.nil?
409
408
  widget_class = "Netzke::#{short_class_name}".constantize
410
409
 
411
410
  conf = weak_children_config.
@@ -431,7 +430,7 @@ module Netzke
431
430
  end
432
431
 
433
432
  def widget_action(action_name)
434
- "#{@id_name}__#{action_name}"
433
+ "#{@global_id}__#{action_name}"
435
434
  end
436
435
 
437
436
  # called when the method_missing tries to processes a non-existing aggregatee
@@ -458,29 +457,54 @@ module Netzke
458
457
  widget_session.clear
459
458
  end
460
459
 
461
- # API: provides all that is necessary for the browser to render a widget.
462
- # <tt>params</tt>
460
+ # Returns global id of a widget in the hierarchy, based on passed reference that follows
461
+ # the double-underscore notation. Referring to "parent" is allowed. If going to far up the hierarchy will
462
+ # result in <tt>nil</tt>, while referring to a non-existent aggregatee will simply provide an erroneous ID.
463
+ # Example:
464
+ # <tt>parent__parent__child__subchild</tt> will traverse the hierarchy 2 levels up, then going down to "child",
465
+ # and further to "subchild". If such a widget exists in the hierarchy, its global id will be returned, otherwise
466
+ # <tt>nil</tt> will be returned.
467
+ def global_id_by_reference(ref)
468
+ ref = ref.to_s
469
+ return parent && parent.global_id if ref == "parent"
470
+ substr = ref.sub(/^parent__/, "")
471
+ if substr == ref # there's no "parent__" in the beginning
472
+ return global_id + "__" + ref
473
+ else
474
+ return parent.global_id_by_reference(substr)
475
+ end
476
+ end
477
+
478
+ # API: provides what is necessary for the browser to render a widget.
479
+ # <tt>params</tt> should contain:
480
+ # * <tt>:cache</tt> - an array of widget classes cached at the browser
481
+ # * <tt>:id</tt> - reference to the aggregatee
482
+ # * <tt>:container</tt> - Ext id of the container where in which the aggregatee will be rendered
463
483
  def load_aggregatee_with_cache(params)
464
484
  cache = ActiveSupport::JSON.decode(params.delete(:cache))
465
- relative_widget_id = params.delete(:id).underscore
466
- widget = aggregatee_instance(relative_widget_id)
485
+ relative_widget_id = params.delete(:id).underscore.to_sym
486
+ widget = aggregatees[relative_widget_id] && aggregatee_instance(relative_widget_id)
467
487
 
468
- # inform the widget that it's being loaded
469
- widget.before_load
488
+ if widget
489
+ # inform the widget that it's being loaded
490
+ widget.before_load
470
491
 
471
- [{
472
- :js => widget.js_missing_code(cache),
473
- :css => widget.css_missing_code(cache)
474
- }, {
475
- :render_widget_in_container => {
476
- :container => params[:container],
477
- :config => widget.js_config
478
- }
479
- }, {
480
- :widget_loaded => {
481
- :id => relative_widget_id
482
- }
483
- }]
492
+ [{
493
+ :js => widget.js_missing_code(cache),
494
+ :css => widget.css_missing_code(cache)
495
+ }, {
496
+ :render_widget_in_container => { # TODO: rename it
497
+ :container => params[:container],
498
+ :config => widget.js_config
499
+ }
500
+ }, {
501
+ :widget_loaded => {
502
+ :id => relative_widget_id
503
+ }
504
+ }]
505
+ else
506
+ {:feedback => "Couldn't load aggregatee '#{relative_widget_id}'"}
507
+ end
484
508
  end
485
509
 
486
510
  # Method dispatcher - instantiates an aggregatee and calls the method on it
@@ -30,7 +30,7 @@ module Netzke
30
30
  res = {}
31
31
 
32
32
  # Unique id of the widget
33
- res.merge!(:id => id_name)
33
+ res.merge!(:id => global_id)
34
34
 
35
35
  # Recursively include configs of all non-late aggregatees, so that the widget can instantiate them
36
36
  # in javascript immediately.
@@ -42,10 +42,11 @@ module Netzke
42
42
 
43
43
  # Api (besides the default "load_aggregatee_with_cache" - JavaScript side already knows about it)
44
44
  api_points = self.class.api_points.reject{ |p| p == :load_aggregatee_with_cache }
45
- res.merge!(:api => api_points) unless api_points.empty?
45
+ res.merge!(:netzke_api => api_points) unless api_points.empty?
46
46
 
47
47
  # Widget class name. Needed for dynamic instantiation in javascript.
48
- res.merge!(:widget_class_name => short_widget_class_name)
48
+ # res.merge!(:widget_class_name => short_widget_class_name)
49
+ res.merge!(:scoped_class_name => self.class.js_scoped_class_name)
49
50
 
50
51
  # Actions, toolbars and menus
51
52
  # tools && res.merge!(:tools => tools)
@@ -56,8 +57,8 @@ module Netzke
56
57
  res[:persistent_config] = persistent_config_enabled?
57
58
 
58
59
  # Merge with all config options passed as hash to config[:ext_config]
60
+ logger.debug "!!! ext_config: #{ext_config.inspect}\n"
59
61
  res.merge!(ext_config)
60
-
61
62
 
62
63
  res
63
64
  end
@@ -84,12 +85,18 @@ module Netzke
84
85
 
85
86
  # instantiating
86
87
  def js_widget_instance
87
- %Q{var #{name.jsonify} = new Ext.netzke.cache.#{short_widget_class_name}(#{js_config.to_nifty_json});}
88
+ %Q{var #{name.jsonify} = new #{self.class.js_full_class_name}(#{js_config.to_nifty_json});}
88
89
  end
89
90
 
90
91
  # rendering
91
92
  def js_widget_render
92
- %Q{#{name.jsonify}.render("#{name.to_s.split('_').join('-')}-div");}
93
+ %Q{
94
+ if (#{name.jsonify}.isXType("netzkewindow")) {
95
+ #{name.jsonify}.show();
96
+ } else {
97
+ #{name.jsonify}.render("#{name.to_s.split('_').join('-')}-div");
98
+ }
99
+ }
93
100
  end
94
101
 
95
102
  # container for rendering
@@ -121,17 +128,39 @@ module Netzke
121
128
  "Ext.Panel"
122
129
  end
123
130
 
124
- # functions and properties that will be used to extend the functionality of (Ext) JS-class specified in js_base_class
131
+ # Properties (including methods) that will be used to extend the functionality of (Ext) JS-class specified in js_base_class
125
132
  def js_extend_properties
126
133
  {}
127
134
  end
135
+
136
+ # Returns the scope of this widget,
137
+ # e.g. "Netzke.GridPanelLib"
138
+ def js_scope
139
+ js_full_class_name.split(".")[0..-2].join(".")
140
+ end
141
+
142
+ # Returns the name of the JavaScript class for this widget, including the scopes,
143
+ # e.g.: "Netzke.GridPanelLib.RecordFormWindow"
144
+ def js_scoped_class_name
145
+ name.gsub("::", ".")
146
+ end
147
+
148
+ # Returns the full name of the JavaScript class, including the scopes *and* the common scope, which is
149
+ # Netzke.classes.
150
+ # E.g.: "Netzke.classes.Netzke.GridPanelLib.RecordFormWindow"
151
+ def js_full_class_name
152
+ "Netzke.classes." + js_scoped_class_name
153
+ end
154
+
155
+ # Builds this widget's xtype
156
+ # E.g.: netzkewindow, netzkegridpanel
157
+ def js_xtype
158
+ name.gsub("::", "").downcase
159
+ end
128
160
 
129
161
  # widget's menus
130
162
  def js_menus; []; end
131
163
 
132
- # items
133
- # def js_items; null; end
134
-
135
164
  # are we using JS inheritance? for now, if js_base_class is a Netzke class - yes
136
165
  def js_inheritance?
137
166
  superclass != Netzke::Base
@@ -143,30 +172,39 @@ module Netzke
143
172
  if js_inheritance?
144
173
  # In case of using javascript inheritance, little needs to be done
145
174
  <<-END_OF_JAVASCRIPT
175
+ // Define the scope
176
+ Ext.ns("#{js_scope}");
146
177
  // Create the class
147
- Ext.netzke.cache.#{short_widget_class_name} = function(config){
148
- Ext.netzke.cache.#{short_widget_class_name}.superclass.constructor.call(this, config);
178
+ #{js_full_class_name} = function(config){
179
+ #{js_full_class_name}.superclass.constructor.call(this, config);
149
180
  };
150
181
  // Extend it with the class that we inherit from, and mix in js_extend_properties
151
- Ext.extend(Ext.netzke.cache.#{short_widget_class_name}, Ext.netzke.cache.#{superclass.short_widget_class_name}, Ext.applyIf(#{js_extend_properties.to_nifty_json}, Ext.widgetMixIn));
182
+ Ext.extend(#{js_full_class_name}, #{superclass.js_full_class_name}, Ext.applyIf(#{js_extend_properties.to_nifty_json}, Ext.widgetMixIn));
183
+ // Register xtype
184
+ Ext.reg("#{js_xtype}", #{js_full_class_name});
185
+
152
186
  END_OF_JAVASCRIPT
187
+
153
188
  else
154
189
  js_add_menus = "this.addMenus(#{js_menus.to_nifty_json});" unless js_menus.empty?
155
190
  <<-END_OF_JAVASCRIPT
191
+ // Define the scope
192
+ Ext.ns("#{js_scope}");
156
193
  // Constructor
157
- Ext.netzke.cache.#{short_widget_class_name} = function(config){
194
+ #{js_full_class_name} = function(config){
158
195
  // Do all the initializations that every Netzke widget should do: create methods for API-points,
159
196
  // process actions, tools, toolbars
160
197
  this.commonBeforeConstructor(config);
161
-
162
198
  // Call the constructor of the inherited class
163
- Ext.netzke.cache.#{short_widget_class_name}.superclass.constructor.call(this, config);
164
-
199
+ #{js_full_class_name}.superclass.constructor.call(this, config);
165
200
  // What every widget should do after calling the constructor of the inherited class, like
166
201
  // setting extra events
167
202
  this.commonAfterConstructor(config);
168
203
  };
169
- Ext.extend(Ext.netzke.cache.#{short_widget_class_name}, #{js_base_class}, Ext.applyIf(#{js_extend_properties.to_nifty_json}, Ext.widgetMixIn));
204
+ Ext.extend(#{js_full_class_name}, #{js_base_class}, Ext.applyIf(#{js_extend_properties.to_nifty_json}, Ext.widgetMixIn));
205
+ // Register xtype
206
+ Ext.reg("#{js_xtype}", #{js_full_class_name});
207
+
170
208
  END_OF_JAVASCRIPT
171
209
  end
172
210
  end
@@ -9,8 +9,6 @@ module Netzke
9
9
  Netzke::Base.session = session
10
10
  session[:netzke_user_id] = defined?(current_user) ? current_user.try(:id) : nil
11
11
 
12
- Netzke::Base.user = defined?(current_user) ? current_user : nil # for backward compatibility (TODO: eliminate the need for this)
13
-
14
12
  # set netzke_just_logged_in and netzke_just_logged_out states (may be used by Netzke widgets)
15
13
  if session[:_netzke_next_request_is_first_after_login]
16
14
  session[:netzke_just_logged_in] = true
@@ -44,10 +42,8 @@ module Netzke
44
42
 
45
43
  # instantiate the server part of the widget
46
44
  widget_instance = widget_class.new(self.class.widget_config_storage[widget])
47
- # (OLD VERSION)
48
- # widget_instance = widget_class.new(self.class.widget_config_storage[widget].merge(:controller => self)) # OPTIMIZE: top-level widgets have access to the controller - can we avoid that?
49
-
50
- render :text => widget_instance.send(api_action, params)
45
+
46
+ render :text => widget_instance.send(api_action, params), :layout => false
51
47
  end
52
48
  end
53
49
  end
@@ -40,6 +40,24 @@ class Hash
40
40
  h
41
41
  end
42
42
  end
43
+
44
+ # add flatten_with_type method to Hash
45
+ def flatten_with_type(preffix = "")
46
+ res = []
47
+ self.each_pair do |k,v|
48
+ name = ((preffix.to_s.empty? ? "" : preffix.to_s + "__") + k.to_s).to_sym
49
+ if v.is_a?(Hash)
50
+ res += v.flatten_with_type(name)
51
+ else
52
+ res << {
53
+ :name => name,
54
+ :value => v,
55
+ :type => (["TrueClass", "FalseClass"].include?(v.class.name) ? 'Boolean' : v.class.name).to_sym
56
+ }
57
+ end
58
+ end
59
+ res
60
+ end
43
61
 
44
62
  # Javascrit-like access to Hash values
45
63
  def method_missing(method, *args)
@@ -40,27 +40,31 @@ class CoreExtTest < ActiveSupport::TestCase
40
40
  assert_equal({:aB => 1, "cD" => [[1, {:eF => "stay_same"}], {"literal_symbol" => :should_not_change, "literal_string".l => "also_should_not"}]}, {:a_b => 1, "c_d" => [[1, {:e_f => "stay_same"}], {:literal_symbol.l => :should_not_change, "literal_string".l => "also_should_not"}]}.jsonify)
41
41
  end
42
42
 
43
- # test "flatten" do
44
- # assert_equal([{
45
- # :name => :one, :value => 1
46
- # },{
47
- # :name => :two, :value => 2
48
- # },{
49
- # :name => :three__four, :value => 4
50
- # },{
51
- # :name => :three__five__six, :value => 6
52
- # }],
53
- # {
54
- # :one => 1,
55
- # :two => 2,
56
- # :three => {
57
- # :four => 4,
58
- # :five => {
59
- # :six => 6
60
- # }
61
- # }
62
- # }.flatten
63
- # )
64
- # end
43
+ test "flatten_with_type" do
44
+ test_flatten_with_type = {
45
+ :one => 1,
46
+ :two => 2.5,
47
+ :three => {
48
+ :four => true,
49
+ :five => {
50
+ :six => "a string"
51
+ }
52
+ }
53
+ }.flatten_with_type
54
+
55
+ assert_equal(4, test_flatten_with_type.size)
56
+
57
+ test_flatten_with_type.each do |i|
58
+ assert([{
59
+ :name => :one, :value => 1, :type => :Fixnum
60
+ },{
61
+ :name => :two, :value => 2.5, :type => :Float
62
+ },{
63
+ :name => :three__four, :value => true, :type => :Boolean
64
+ },{
65
+ :name => :three__five__six, :value => "a string", :type => :String
66
+ }].include?(i))
67
+ end
68
+ end
65
69
 
66
70
  end
@@ -43,6 +43,14 @@ module Netzke
43
43
  end
44
44
 
45
45
  class DeepNestedWidget < Base
46
+ def initial_aggregatees
47
+ {
48
+ :nested => {:widget_class_name => "VeryDeepNestedWidget"}
49
+ }
50
+ end
51
+ end
52
+
53
+ class VeryDeepNestedWidget < Base
46
54
  end
47
55
 
48
56
  class JsInheritanceWidget < Widget
@@ -90,31 +98,22 @@ class NetzkeCoreTest < ActiveSupport::TestCase
90
98
  assert_kind_of DeepNestedWidget, deep_nested_widget
91
99
 
92
100
  # check the internal names of aggregation instances
93
- assert_equal 'my_widget', widget.id_name
94
- assert_equal 'my_widget__nested_one', nested_widget_one.id_name
95
- assert_equal 'my_widget__nested_two', nested_widget_two.id_name
96
- assert_equal 'my_widget__nested_two__nested', deep_nested_widget.id_name
101
+ assert_equal 'my_widget', widget.global_id
102
+ assert_equal 'my_widget__nested_one', nested_widget_one.global_id
103
+ assert_equal 'my_widget__nested_two', nested_widget_two.global_id
104
+ assert_equal 'my_widget__nested_two__nested', deep_nested_widget.global_id
97
105
  end
98
106
 
99
- # test "permissions" do
100
- # widget = Widget.new
101
- # assert_equal({:read => true, :update => true}, widget.permissions)
102
- #
103
- # widget = Widget.new(:prohibit => :all)
104
- # assert_equal({:read => false, :update => false}, widget.permissions)
105
- #
106
- # widget = Widget.new(:prohibit => :read)
107
- # assert_equal({:read => false, :update => true}, widget.permissions)
108
- #
109
- # widget = Widget.new(:prohibit => [:read, :update])
110
- # assert_equal({:read => false, :update => false}, widget.permissions)
111
- #
112
- # widget = Widget.new(:prohibit => :all, :allow => :read)
113
- # assert_equal({:read => true, :update => false}, widget.permissions)
114
- #
115
- # widget = Widget.new(:prohibit => :all, :allow => [:read, :update])
116
- # assert_equal({:read => true, :update => true}, widget.permissions)
117
- # end
107
+ test "global_id_by_reference" do
108
+ w = Widget.new(:name => "a_widget")
109
+ deep_nested_widget = w.aggregatee_instance(:nested_two__nested)
110
+ assert_equal("a_widget__nested_two", deep_nested_widget.global_id_by_reference(:parent))
111
+ assert_equal("a_widget", deep_nested_widget.global_id_by_reference(:parent__parent))
112
+ assert_equal("a_widget__nested_one", deep_nested_widget.global_id_by_reference(:parent__parent__nested_one))
113
+ assert_equal("a_widget__nested_two__nested__nested", deep_nested_widget.global_id_by_reference(:nested))
114
+ assert_equal("a_widget__nested_two__nested__non_existing", deep_nested_widget.global_id_by_reference(:non_existing))
115
+ assert_nil(deep_nested_widget.global_id_by_reference(:parent__parent__parent)) # too far up
116
+ end
118
117
 
119
118
  test "default config" do
120
119
  widget = Widget.new
@@ -145,8 +144,8 @@ class NetzkeCoreTest < ActiveSupport::TestCase
145
144
 
146
145
  test "js inheritance" do
147
146
  widget = JsInheritanceWidget.new
148
- assert(widget.js_missing_code.index("Ext.netzke.cache.JsInheritanceWidget"))
149
- assert(widget.js_missing_code.index("Ext.netzke.cache.Widget"))
147
+ assert(widget.js_missing_code.index("Netzke.classes.Netzke.JsInheritanceWidget"))
148
+ assert(widget.js_missing_code.index("Netzke.classes.Netzke.Widget"))
150
149
  end
151
150
 
152
151
  test "class-level configuration" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: netzke-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergei Kozlov
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-12 00:00:00 -05:00
12
+ date: 2009-11-08 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: Build ExtJS/Rails widgets with minimum effort
16
+ description: Allows building ExtJS/Rails reusable code in a DRY way
17
17
  email: sergei@playcode.nl
18
18
  executables: []
19
19
 
@@ -31,7 +31,6 @@ files:
31
31
  - README.rdoc
32
32
  - Rakefile
33
33
  - TODO
34
- - VERSION
35
34
  - autotest/discover.rb
36
35
  - generators/netzke_core/USAGE
37
36
  - generators/netzke_core/netzke_core_generator.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.4.4