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 +14 -0
- data/README.rdoc +7 -2
- data/Rakefile +7 -5
- data/javascripts/core.js +88 -48
- data/lib/netzke/base.rb +233 -209
- data/lib/netzke/base_js.rb +56 -18
- data/lib/netzke/controller_extensions.rb +2 -6
- data/lib/netzke/core_ext.rb +18 -0
- data/test/unit/core_ext_test.rb +26 -22
- data/test/unit/netzke_core_test.rb +24 -25
- metadata +3 -4
- data/VERSION +0 -1
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 = "
|
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::
|
13
|
-
|
14
|
-
|
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
|
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
|
-
|
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
|
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
|
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(
|
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(
|
268
|
-
var
|
269
|
-
|
270
|
-
|
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
|
-
//
|
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
|
-
|
307
|
-
|
308
|
-
|
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
|
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
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
//
|
355
|
+
// Code run before calling Ext's constructor - normalizing config to provide Netzke additional functionality
|
337
356
|
commonBeforeConstructor : function(config){
|
338
|
-
this.
|
339
|
-
|
340
|
-
//
|
341
|
-
|
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
|
-
//
|
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.
|
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)
|
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
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
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
|
-
|
499
|
-
|
500
|
-
this.
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
}
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
89
|
+
# Access to controller sessions
|
90
|
+
def self.session
|
91
|
+
@@session ||= {}
|
92
|
+
end
|
94
93
|
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
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
|
-
#
|
165
|
-
def
|
166
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
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
|
-
@
|
173
|
+
@global_id = parent.nil? ? @name : "#{parent.global_id}__#{@name}"
|
183
174
|
@flash = []
|
184
175
|
end
|
185
176
|
|
186
|
-
#
|
187
|
-
|
188
|
-
|
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
|
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
|
-
#
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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(
|
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[
|
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("#{
|
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.
|
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.
|
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
|
-
"#{@
|
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
|
-
#
|
462
|
-
#
|
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
|
-
|
469
|
-
|
488
|
+
if widget
|
489
|
+
# inform the widget that it's being loaded
|
490
|
+
widget.before_load
|
470
491
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
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
|
data/lib/netzke/base_js.rb
CHANGED
@@ -30,7 +30,7 @@ module Netzke
|
|
30
30
|
res = {}
|
31
31
|
|
32
32
|
# Unique id of the widget
|
33
|
-
res.merge!(:id =>
|
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!(:
|
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
|
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{
|
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
|
-
#
|
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
|
-
|
148
|
-
|
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(
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
48
|
-
|
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
|
data/lib/netzke/core_ext.rb
CHANGED
@@ -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)
|
data/test/unit/core_ext_test.rb
CHANGED
@@ -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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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.
|
94
|
-
assert_equal 'my_widget__nested_one', nested_widget_one.
|
95
|
-
assert_equal 'my_widget__nested_two', nested_widget_two.
|
96
|
-
assert_equal 'my_widget__nested_two__nested', deep_nested_widget.
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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("
|
149
|
-
assert(widget.js_missing_code.index("
|
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
|
+
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-
|
12
|
+
date: 2009-11-08 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
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
|