netzke-core 0.4.4 → 0.4.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.
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