netzke-core 0.3.1 → 0.4.2

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/.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
+ });