netzke-core 0.3.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1 @@
1
+ require 'autotest/redgreen'
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.log
2
+ pkg
3
+ doc
4
+ .DS_Store
data/CHANGELOG CHANGED
@@ -1,4 +1,25 @@
1
+ v0.4.2
2
+ 2009-09-11
3
+ Fix: the API call (at the JavaScript side) was ignoring the callback parameter.
4
+ Impr: if the array of API points is empty, it's not added into js_config anymore.
5
+ New: new testing widgets in netzke_controller.
6
+ Fix: extra CSS includes now take effect.
7
+ New: Support for masquerading as "World". In this mode all the "touched" persistent preferences will be overwritten for all roles and users.
8
+
9
+ v0.4.1
10
+ 2009-09-06
11
+ Version bumb to force github rebuild the gem (Manifest is now included)
12
+
13
+ v0.4.0
14
+ 2009-09-05
15
+ Major refactoring.
16
+
17
+ v0.3.2
18
+ 2009-06-05
19
+ Netzke doesn't overwrite session[:user] anymore to not cause authentication-related problems.
20
+
1
21
  v0.3.1
22
+ 2009-05-07
2
23
  Fix: persistent_config_manager can now be set to nil, and it will work fine
3
24
 
4
25
  v0.3.0
data/Manifest CHANGED
@@ -1,28 +1,27 @@
1
- autotest/discover.rb
2
1
  CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.rdoc
5
+ Rakefile
6
+ TODO
7
+ autotest/discover.rb
8
+ generators/netzke_core/USAGE
3
9
  generators/netzke_core/netzke_core_generator.rb
4
10
  generators/netzke_core/templates/create_netzke_preferences.rb
5
- generators/netzke_core/USAGE
6
11
  init.rb
7
12
  install.rb
8
13
  javascripts/core.js
9
14
  lib/app/controllers/netzke_controller.rb
10
15
  lib/app/models/netzke_preference.rb
16
+ lib/netzke-core.rb
11
17
  lib/netzke/action_view_ext.rb
12
18
  lib/netzke/base.rb
13
- lib/netzke/base_extras/interface.rb
14
- lib/netzke/base_extras/js_builder.rb
19
+ lib/netzke/base_js.rb
15
20
  lib/netzke/controller_extensions.rb
16
21
  lib/netzke/core_ext.rb
17
22
  lib/netzke/feedback_ghost.rb
18
23
  lib/netzke/routing.rb
19
- lib/netzke-core.rb
20
- lib/vendor/facets/hash/recursive_merge.rb
21
- LICENSE
22
- Manifest
23
24
  netzke-core.gemspec
24
- Rakefile
25
- README.mdown
26
25
  stylesheets/core.css
27
26
  tasks/netzke_core_tasks.rake
28
27
  test/app_root/app/controllers/application_controller.rb
@@ -48,5 +47,4 @@ test/test_helper.rb
48
47
  test/unit/core_ext_test.rb
49
48
  test/unit/netzke_core_test.rb
50
49
  test/unit/netzke_preference_test.rb
51
- TODO
52
50
  uninstall.rb
data/README.rdoc ADDED
@@ -0,0 +1,12 @@
1
+ = netzke-core
2
+ Create Ext JS + Rails reusable components (widgets) with minimum effort.
3
+
4
+ Introduction to Netzke framework: http://github.com/skozlov/netzke/tree/master
5
+
6
+ Tutorials: http://blog.writelesscode.com
7
+
8
+ Live-demo: http://netzke-demo.writelesscode.com
9
+
10
+ Also see netzke-basepack (pre-programmed widgets) project: http://github.com/skozlov/netzke-basepack/tree/master
11
+
12
+ Copyright (c) 2009 Sergei Kozlov, released under the MIT license
data/Rakefile CHANGED
@@ -1,14 +1,18 @@
1
- require 'echoe'
2
-
3
- Echoe.new("netzke-core") do |p|
4
- p.author = "Sergei Kozlov"
5
- p.email = "sergei@writelesscode.com"
6
- p.summary = "Build ExtJS/Rails widgets with minimum effort"
7
- p.url = "http://writelesscode.com"
8
- p.development_dependencies = []
9
- p.test_pattern = 'test/**/*_test.rb'
10
- p.retain_gemspec = true
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "netzke-core"
5
+ gemspec.summary = "Build ExtJS/Rails widgets with minimum effort"
6
+ gemspec.description = "Build ExtJS/Rails widgets with minimum effort"
7
+ gemspec.email = "sergei@playcode.nl"
8
+ gemspec.homepage = "http://github.com/skozlov/netzke-core"
9
+ gemspec.rubyforge_project = "netzke-core"
10
+ gemspec.authors = ["Sergei Kozlov"]
11
+ end
12
+ Jeweler::RubyforgeTasks.new do |rubyforge|
13
+ rubyforge.doc_task = "rdoc"
14
+ end
11
15
 
12
- # fixing the problem with lib/*-* files being removed while doing manifest
13
- p.clean_pattern = ["pkg", "doc", 'build/*', '**/coverage', '**/*.o', '**/*.so', '**/*.a', '**/*.log', "{ext,lib}/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/Makefile", "{ext,lib}/**/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/**/Makefile", "pkg", "*.gem", ".config"]
14
- end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
data/TODO CHANGED
@@ -1 +1,2 @@
1
- * Re-factor JS-level inheritance mechanisms
1
+ * Re-factor JS-level inheritance mechanisms (2009-07-5 why?)
2
+ * Get rid of the default_config method, because the same functionality maybe achieved by overwriting the initialize method
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.2
@@ -3,7 +3,7 @@ class CreateNetzkePreferences < ActiveRecord::Migration
3
3
  create_table :netzke_preferences do |t|
4
4
  t.string :name
5
5
  t.string :pref_type
6
- t.string :value
6
+ t.string :value, :limit => 65535
7
7
  t.integer :user_id
8
8
  t.integer :role_id
9
9
  t.string :widget_name
data/javascripts/core.js CHANGED
@@ -9,24 +9,17 @@ Ext.netzke.cache = {};
9
9
 
10
10
  Ext.QuickTips.init(); // seems obligatory in Ext v2.2.1, otherwise Ext.Component#destroy() stops working properly
11
11
 
12
- // to comply with Rails' forgery protection
12
+ // To comply with Rails' forgery protection
13
13
  Ext.Ajax.extraParams = {
14
14
  authenticity_token : Ext.authenticityToken
15
15
  };
16
16
 
17
- // helper method to do multiple Ext.apply's
18
- Ext.netzke.chainApply = function(objectArray){
19
- var res = {};
20
- Ext.each(objectArray, function(obj){Ext.apply(res, obj)});
21
- return res;
22
- };
23
-
24
17
  // Type detection functions
25
18
  Netzke.isObject = function(o) {
26
19
  return (o != null && typeof o == "object" && o.constructor.toString() == Object.toString());
27
20
  }
28
21
 
29
- // Some Rubyish String extensions
22
+ // Some Ruby-ish String extensions
30
23
  // from http://code.google.com/p/inflection-js/
31
24
  String.prototype.camelize=function(lowFirstLetter)
32
25
  {
@@ -66,7 +59,6 @@ Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
66
59
  var sid = this.meta ? this.meta.id : null;
67
60
  var recordType = this.recordType, fields = recordType.prototype.fields;
68
61
  var records = [];
69
- // console.info(this.meta);
70
62
  var root = o[this.meta.root] || o, totalRecords = o[this.meta.totalProperty], success = o[this.meta.successProperty];
71
63
  for(var i = 0; i < root.length; i++){
72
64
  var n = root[i];
@@ -91,8 +83,174 @@ Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
91
83
  }
92
84
  });
93
85
 
94
- // Methods common to all widget classes
86
+ // Properties/methods common to all widget classes
95
87
  Ext.widgetMixIn = {
88
+ height: 400,
89
+ width: 800,
90
+ border: false,
91
+ is_netzke: true, // to distinguish Netzke components from regular Ext components
92
+
93
+ /*
94
+ Loads aggregatee into a container. Sends the widgets cache info to the server.
95
+ */
96
+ loadAggregatee: function(params){
97
+ // build the cached widget list
98
+ var cachedWidgetNames = [];
99
+ for (name in Ext.netzke.cache) {
100
+ cachedWidgetNames.push(name);
101
+ }
102
+
103
+ params.cache = Ext.encode(cachedWidgetNames);
104
+
105
+ // remember the passed callback
106
+ if (params.callback) {
107
+ this.callbackHash[params.id] = params.callback;
108
+ delete params.callback;
109
+ delete params.scope;
110
+ }
111
+
112
+ // visually disable the container while the widget is being loaded
113
+ // Ext.getCmp(params.container).disable();
114
+ Ext.getCmp(params.container).removeChild(); // simply cleanup the area, which speaks for itself
115
+
116
+ // remote api call
117
+ this.loadAggregateeWithCache(params);
118
+ },
119
+
120
+ /*
121
+ Called by the server as callback about loaded widget
122
+ */
123
+ widgetLoaded : function(params){
124
+ if (this.fireEvent('widgetload')) {
125
+ // Enable the container after the widget is succesfully loaded
126
+ // this.getChildWidget(params.id).ownerCt.enable();
127
+
128
+ // provide the callback to that widget that was loading the child, passing the child itself
129
+ var callbackFn = this.callbackHash[params.id.camelize(true)];
130
+ if (callbackFn) {
131
+ callbackFn.call(params.scope || this, this.getChildWidget(params.id));
132
+ delete this.callbackHash[params.id.camelize(true)];
133
+ }
134
+ }
135
+ },
136
+
137
+ /*
138
+ Returns the parent widget
139
+ */
140
+ getParent: function(){
141
+ // simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
142
+ var idSplit = this.id.split("__");
143
+ idSplit.pop();
144
+ var parentId = idSplit.join("__");
145
+
146
+ return parentId === "" ? null : Ext.getCmp(parentId);
147
+ },
148
+
149
+ /*
150
+ Reloads current widget (calls the parent to reload it as its aggregatee)
151
+ */
152
+ reload : function(){
153
+ var parent = this.getParent();
154
+ if (parent) {
155
+ parent.loadAggregatee({id:this.localId(parent), container:this.ownerCt.id});
156
+ } else {
157
+ window.location.reload();
158
+ }
159
+ },
160
+
161
+ /*
162
+ Gets id in the context of provided parent.
163
+ For example, the widgets "properties", being a child of "books" has global id "books__properties",
164
+ which *is* its widegt's real id. This methods, with the instance of "books" passed as parameter,
165
+ returns "properties".
166
+ */
167
+ localId : function(parent){
168
+ return this.id.replace(parent.id + "__", "");
169
+ },
170
+
171
+ /*
172
+ Instantiates and inserts a widget into a container with layout 'fit'.
173
+ Arg: an JS object with the following keys:
174
+ - id: id of the receiving container
175
+ - config: configuration of the widget to be instantiated and inserted into the container
176
+ */
177
+ renderWidgetInContainer : function(params){
178
+ var cont = Ext.getCmp(params.container);
179
+ cont.instantiateChild(params.config);
180
+ },
181
+
182
+ /*
183
+ Reconfigures the widget
184
+ */
185
+ reconfigure: function(config){
186
+ this.ownerCt.instantiateChild(config)
187
+ },
188
+
189
+ /*
190
+ Evaluates CSS
191
+ */
192
+ css : function(code){
193
+ var linkTag = document.createElement('style');
194
+ linkTag.type = 'text/css';
195
+ linkTag.innerHTML = code;
196
+ document.body.appendChild(linkTag);
197
+ },
198
+
199
+ /*
200
+ Evaluates JS
201
+ */
202
+ js : function(code){
203
+ eval(code);
204
+ },
205
+
206
+ /*
207
+ Executes a bunch of methods. This method is called almost every time a communication to the server takes place.
208
+ Thus the server side of a widget can provide any set of commands to its client side.
209
+ Args:
210
+ - instructions: array of methods, in the order of execution.
211
+ Each item is an object in one of the following 2 formats:
212
+ 1) {method1:args1, method2:args2}, where methodN is a name of a public method of this widget; these methods are called in no particular order
213
+ 2) {widget:widget_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child widget
214
+
215
+ Example:
216
+ - [
217
+ // the same as this.feedback("Your order is accepted")
218
+ {feedback: "You order is accepted"},
219
+
220
+ // the same as this.getChildWidget('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}])
221
+ {widget:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] },
222
+
223
+ // ... etc:
224
+ {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}},
225
+ {setColums:[{},{}]},
226
+ {setMenus:[{},{}]},
227
+ ...
228
+ ]
229
+ */
230
+ bulkExecute : function(instructions){
231
+ if (Ext.isArray(instructions)) {
232
+ Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this);
233
+ } else {
234
+ for (var instr in instructions) {
235
+ if (this[instr]) {
236
+ this[instr].apply(this, [instructions[instr]]);
237
+ } else {
238
+ var childWidget = this.getChildWidget(instr);
239
+ if (childWidget) {
240
+ childWidget.bulkExecute(instructions[instr]);
241
+ } else {
242
+ throw "Unknown method or child widget '" + instr +"' in widget '" + this.id + "'"
243
+ }
244
+ }
245
+ }
246
+ }
247
+ },
248
+
249
+ // Get the child widget
250
+ getChildWidget : function(id){
251
+ return id === 'parent' ? this.getParent() : Ext.getCmp(this.id+"__"+id);
252
+ },
253
+
96
254
  // Common handler for actions
97
255
  actionHandler : function(action){
98
256
  // If firing corresponding event doesn't return false, call the handler
@@ -109,11 +267,72 @@ Ext.widgetMixIn = {
109
267
  }
110
268
  },
111
269
 
112
- beforeConstructor : function(config){
270
+ // Does the call to the server and processes the response
271
+ callServer : function(intp, params, callback, scope){
272
+ if (!params) params = {};
273
+ Ext.Ajax.request({
274
+ params : params,
275
+ url : this.id + "__" + intp,
276
+ callback : function(options, success, response){
277
+ if (success) {
278
+ // execute commands from server
279
+ this.bulkExecute(Ext.decode(response.responseText));
280
+
281
+ // provade callback if needed
282
+ if (typeof callback == 'function') {
283
+ if (!scope) scope = this;
284
+ callback.apply(scope);
285
+ }
286
+ }
287
+ },
288
+ scope : this
289
+ });
290
+ },
291
+
292
+ /* Parse the bbar and tbar (both Arrays), replacing the strings with the corresponding methods. For example:
293
+ replaceStringsWithActions( ['add', {text:'Menu', menu:['edit', 'delete']}] )
294
+ => [scope.actions['add'], {text:'Menu', menu:[scope.actions['edit'], scope.actions['delete']]}]
295
+ */
296
+ normalizeMenuItems: function(arry, scope){
297
+ var res = []; // new array
298
+ Ext.each(arry, function(o){
299
+ if (typeof o === "string") {
300
+ var camelized = o.camelize(true);
301
+ if (scope.actions[camelized]){
302
+ res.push(scope.actions[camelized]);
303
+ } else {
304
+ // if there's no action with this name, maybe it's a separator or something
305
+ res.push(o);
306
+ }
307
+ } else if (Netzke.isObject(o)) {
308
+ // look inside the objects...
309
+ for (var key in o) {
310
+ if (Ext.isArray(o[key])) {
311
+ // ... and recursively process inner arrays found
312
+ o[key] = this.normalizeMenuItems(o[key], scope);
313
+ }
314
+ }
315
+ res.push(o);
316
+ }
317
+ }, this);
318
+ return res;
319
+ },
320
+
321
+
322
+ // Every Netzke widget
323
+ commonBeforeConstructor : function(config){
113
324
  this.actions = {};
114
325
 
326
+ // Generate methods for api points
327
+ if (!config.api) { config.api = []; }
328
+ config.api.push('load_aggregatee_with_cache'); // all netzke widgets get this API
329
+ Ext.each(config.api, function(intp){
330
+ this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
331
+ }, this);
332
+
115
333
  // Create Ext.Actions based on config.actions
116
334
  if (config.actions) {
335
+ this.testActions = {};
117
336
  for (var name in config.actions) {
118
337
  // Create an event for each action (so that higher-level widgets could interfere)
119
338
  this.addEvents(name+'click');
@@ -125,37 +344,13 @@ Ext.widgetMixIn = {
125
344
  this.actions[name] = new Ext.Action(actionConfig);
126
345
  }
127
346
 
128
- /* Parse the bbar and tbar (both Arrays), replacing the strings with the corresponding methods. For example:
129
- replaceStringsWithActions( ['add', {text:'Menu', menu:['edit', 'delete']}] )
130
- => [scope.actions['add'], {text:'Menu', menu:[scope.actions['edit'], scope.actions['delete']]}]
131
- */
132
- var replaceStringsWithActions = function(arry, scope){
133
- var res = []; // new array
134
- Ext.each(arry, function(o){
135
- if (typeof o === "string") {
136
- var camelized = o.camelize(true);
137
- if (scope.actions[camelized]){
138
- res.push(scope.actions[camelized]);
139
- } else {
140
- // if there's no action with this name, maybe it's a separator or something
141
- res.push(o);
142
- }
143
- } else if (Netzke.isObject(o)) {
144
- // look inside the objects...
145
- for (var key in o) {
146
- if (Ext.isArray(o[key])) {
147
- // ... and recursively process inner arrays found
148
- o[key] = replaceStringsWithActions(o[key], scope);
149
- }
150
- }
151
- res.push(o);
152
- }
153
- });
154
- return res;
155
- }
156
- config.bbar = config.bbar && replaceStringsWithActions(config.bbar, this);
157
- config.tbar = config.tbar && replaceStringsWithActions(config.tbar, this);
158
- config.menu = config.menu && replaceStringsWithActions(config.menu, this);
347
+ config.bbar = config.bbar && this.normalizeMenuItems(config.bbar, this);
348
+ config.tbar = config.tbar && this.normalizeMenuItems(config.tbar, this);
349
+ config.menu = config.menu && this.normalizeMenuItems(config.menu, this);
350
+ config.contextMenu = config.contextMenu && this.normalizeMenuItems(config.contextMenu, this);
351
+
352
+ // TODO: need to rethink this action related stuff
353
+ config.actions = this.actions;
159
354
 
160
355
  }
161
356
 
@@ -172,22 +367,31 @@ Ext.widgetMixIn = {
172
367
  config.tools = normTools;
173
368
  }
174
369
 
370
+ // Set title
371
+ if (!config.title) config.title = config.id.humanize();
175
372
  },
176
373
 
177
- afterConstructor : function(config){
374
+ // At this moment component is fully initializied
375
+ commonAfterConstructor : function(config){
376
+ // From everywhere accessible FeedbackGhost
178
377
  this.feedbackGhost = Ext.getCmp('feedback_ghost');
179
378
 
180
- // cleaning up
379
+ // Add the menus
380
+ if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);}
381
+
382
+ // generic events
383
+ this.addEvents(
384
+ 'widgetload' // fired when a child is dynamically loaded
385
+ );
386
+
387
+ // Cleaning up on destroy
181
388
  this.on('beforedestroy', function(){
182
- this.cleanUpMenu(this);
389
+ this.cleanUpMenu();
183
390
  }, this);
184
391
 
185
- // After render, add the menus
186
- this.on('render', function(){
187
- if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu);}
188
- }, this);
392
+ this.callbackHash = {};
189
393
 
190
- this.on('render', this.onWidgetLoad, this);
394
+ if (this.afterConstructor) this.afterConstructor(config);
191
395
  },
192
396
 
193
397
  feedback:function(msg){
@@ -247,7 +451,7 @@ Ext.widgetMixIn = {
247
451
  // Netzke extensions for Ext.Container
248
452
  Ext.override(Ext.Container, {
249
453
  /**
250
- Get Netzke widget that this Ext.Container is part of.
454
+ Get Netzke widget that this Ext.Container is part of (*not* the parent widget, for which call getParent)
251
455
  It searches up the Ext.Container hierarchy until it finds a Container that has isNetzke property set to true
252
456
  (or until it reaches the top).
253
457
  */
@@ -261,70 +465,24 @@ Ext.override(Ext.Container, {
261
465
  return null
262
466
  }
263
467
  }
264
- }
265
- });
266
-
267
- // Make Panel with layout 'fit' capable of dynamic widgets loading
268
- Ext.override(Ext.Panel, {
468
+ },
469
+
470
+ // Get the widget that we are hosting
269
471
  getWidget: function(){
270
- return this.items.get(0);
472
+ 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
271
473
  },
272
474
 
273
- loadWidget: function(url, params){
274
- if (!params) {
275
- params = {};
276
- }
277
-
475
+ removeChild : function(){
476
+ this.remove(this.getWidget());
477
+ },
478
+
479
+ instantiateChild : function(config){
278
480
  this.remove(this.getWidget()); // first delete previous widget
279
-
280
- if (!url) return false; // don't load any widget if the url is null
281
481
 
282
- // we will let the server know which components we have cached
283
- var cachedComponentNames = [];
284
- for (name in Ext.netzke.cache) {
285
- cachedComponentNames.push(name);
286
- }
287
-
288
- this.disable(); // to visually emphasize loading
289
-
290
- Ext.Ajax.request({
291
- url:url,
292
- params:Ext.apply(params, {components_cache:Ext.encode(cachedComponentNames)}),
293
- script:false,
294
- callback:function(panel, success, response){
295
- var responseObj = Ext.decode(response.responseText);
296
- if (responseObj.config) {
297
- // we got a normal response
298
-
299
- // evaluate widget's stylesheets
300
- if (responseObj.css){
301
- var linkTag = document.createElement('style');
302
- linkTag.type = 'text/css';
303
- linkTag.innerHTML = responseObj.css;
304
- document.body.appendChild(linkTag);
305
- }
306
-
307
- // evaluate widget's javascript
308
- if (responseObj.js) {
309
- eval(responseObj.js);
310
- }
311
-
312
- responseObj.config.ownerWidget = this.getOwnerWidget();
313
- var instance = new Ext.netzke.cache[responseObj.config.widgetClassName](responseObj.config)
314
-
315
- this.add(instance);
316
- this.doLayout();
317
-
318
- } else {
319
- // we didn't get normal response - desplay the flash with eventual errors
320
- this.getOwnerWidget().feedback(responseObj.flash);
321
- }
322
-
323
- // reenable the panel
324
- this.enable();
325
- },
326
- scope:this
327
- })
328
- }
329
- });
482
+ if (!config) return false; // simply remove current widget if null is passed
330
483
 
484
+ var instance = new Ext.netzke.cache[config.widgetClassName](config);
485
+ this.add(instance);
486
+ this.doLayout();
487
+ }
488
+ });