skozlov-netzke-core 0.1.0.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGELOG +108 -0
  2. data/LICENSE +2 -19
  3. data/Manifest +50 -0
  4. data/README.rdoc +12 -0
  5. data/Rakefile +2 -3
  6. data/TODO +2 -0
  7. data/autotest/discover.rb +3 -0
  8. data/generators/netzke_core/netzke_core_generator.rb +6 -6
  9. data/generators/netzke_core/templates/create_netzke_preferences.rb +2 -2
  10. data/javascripts/core.js +474 -111
  11. data/lib/app/controllers/netzke_controller.rb +76 -0
  12. data/lib/app/models/netzke_preference.rb +128 -39
  13. data/lib/netzke-core.rb +23 -9
  14. data/lib/netzke/action_view_ext.rb +26 -0
  15. data/lib/netzke/base.rb +440 -102
  16. data/lib/netzke/base_js.rb +258 -0
  17. data/lib/netzke/controller_extensions.rb +80 -29
  18. data/lib/netzke/core_ext.rb +72 -21
  19. data/lib/netzke/feedback_ghost.rb +43 -0
  20. data/lib/netzke/routing.rb +9 -0
  21. data/netzke-core.gemspec +10 -11
  22. data/stylesheets/core.css +12 -0
  23. data/test/app_root/app/controllers/{application.rb → application_controller.rb} +0 -0
  24. data/test/app_root/app/models/role.rb +3 -0
  25. data/test/app_root/app/models/user.rb +3 -0
  26. data/test/app_root/config/boot.rb +2 -1
  27. data/test/app_root/config/database.yml +10 -0
  28. data/test/app_root/config/environment.rb +1 -0
  29. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +18 -0
  30. data/test/app_root/db/migrate/20090423214303_create_roles.rb +11 -0
  31. data/test/app_root/db/migrate/20090423222114_create_users.rb +12 -0
  32. data/test/app_root/lib/console_with_fixtures.rb +4 -0
  33. data/test/app_root/script/console +1 -0
  34. data/test/fixtures/roles.yml +7 -0
  35. data/test/fixtures/users.yml +9 -0
  36. data/test/test_helper.rb +3 -2
  37. data/test/unit/core_ext_test.rb +66 -0
  38. data/test/unit/netzke_core_test.rb +167 -0
  39. data/test/unit/netzke_preference_test.rb +103 -0
  40. metadata +45 -30
  41. data/README.mdown +0 -11
  42. data/generators/netzke_core/templates/create_netzke_layouts.rb +0 -14
  43. data/generators/netzke_core/templates/netzke.html.erb +0 -10
  44. data/lib/app/models/netzke_layout.rb +0 -75
  45. data/lib/netzke/js_class_builder.rb +0 -114
  46. data/lib/vendor/facets/hash/recursive_merge.rb +0 -28
  47. data/test/core_ext_test.rb +0 -35
  48. data/test/netzke_core_test.rb +0 -136
data/CHANGELOG CHANGED
@@ -1,3 +1,111 @@
1
+ v0.4.1
2
+ 2009-09-06
3
+ Version bumb to force github rebuild the gem (Manifest is now included)
4
+
5
+ v0.4.0
6
+ 2009-09-05
7
+ Major refactoring.
8
+
9
+ v0.3.2
10
+ 2009-06-05
11
+ Netzke doesn't overwrite session[:user] anymore to not cause authentication-related problems.
12
+
13
+ v0.3.1
14
+ 2009-05-07
15
+ Fix: persistent_config_manager can now be set to nil, and it will work fine
16
+
17
+ v0.3.0
18
+ 2009-05-07
19
+ Refactor: got rid of NetzkeLayout model, now all layouts are stored in netzke_preferences
20
+ New: persistent_config now has a method for_widget that accepts a block
21
+ autotest compatibility
22
+ New: String#to_b converts a string to true/false
23
+ New: Netzke::Base.session introduced for session data
24
+ New: weak_children_config and strong_children_config can now be declared by a widget, which specifies weak and strong configuration that every child of this widget will receive (e.g. display/hide configuration tool)
25
+ Fix: (degradation) flash message is now shown again in case of erroneous attempt to load a widget
26
+ New: widgets now can check session[:netzke_just_logged_in] and session[:netzke_just_logged_out] automatically set by Netzke after login/logout
27
+
28
+ v0.2.11
29
+ Introduction of getOwnerWidget()-method to Ext.Component. It provides the Netzke widget this Component belongs to.
30
+
31
+ v0.2.10
32
+ Removed dependency on 'json' gem.
33
+ Rails v2.3.2 compatibility.
34
+
35
+ v0.2.9
36
+ Actions, toolbars and tools reworked for easier configuration.
37
+ Menus introduced (based on actions).
38
+ Significant code clean-up.
39
+ Bug fix (nasty one): Ext.widgetMixIn was getting messed up along with dynamic widget loading.
40
+ Must work in IE now.
41
+
42
+ v0.2.8
43
+ Support for extra javascripts and stylesheets per widget.
44
+
45
+ v0.2.7
46
+ QuickTips get initialized now, as otherwise Ext 2.2.1 doesn't properly destroy() BoxComponents for me.
47
+
48
+ v0.2.6
49
+ FeedackGhost is now capable of displaying multiple flash messages.
50
+ Dependencies slightly refactored.
51
+ An informative exception added to Base#aggregatee_instance.
52
+ JS-level inheritance enabled.
53
+ Work-around for the problem with Ext 2.2.1 in loadWidget.
54
+ Events "<action_id>click" added to the widgets along with the actions.
55
+ aggregatee_missing method added to Netzke::Base - called when a non-existing aggregate of a widget is tried to be invoked
56
+ Code readability improvements.
57
+
58
+ v0.2.5
59
+ Minor code restructuring.
60
+
61
+ v0.2.4
62
+ Some minor improvements.
63
+
64
+ v0.2.3
65
+ FeedbackGhost will show the feedback on the top of the screen independent of the page scrolling.
66
+ Ext.Panel#loadWidget will accept null as url to delete the currently loaded widget
67
+ Bug fix: persistent_config works again
68
+
69
+ v0.2.2
70
+ js_ext_config instance method added for overwriting
71
+ Multiuser support
72
+ Using Rails.logger for logging
73
+ "config"-class method for every class inheriting Netzke::Base - for class-level configurations
74
+
75
+ v0.2.1
76
+ Fixed the path to ext-base-min.js for production mode.
77
+ Also works in Safari now.
78
+
79
+ v0.2.0
80
+ * Some re-factoring and redesign. Now simple compound widgets can be created on the fly in the controller
81
+ * Added ext_widget[:quiet] configuration option to suppress widget's feedback
82
+ * Support for extra CSS sources, similar to JS
83
+ * NETZKE_BOOT_CONFIG introduced to specify which Netzke functionality should be disabled to reduce the size of /netzke/netzke.[js|css]
84
+ * FeedbackGhost widget added - invisible widget providing feedback to the user
85
+ * netzke_widget controller class-method renamed into netzke
86
+ * JS-comments now get stripped also from the extra files that get included in the netzke-* gems.
87
+ * Permissions joined js_config
88
+ * Bug fixes
89
+
90
+ v0.1.4
91
+ Helpers added to facilitate ExtJS/netzke.js inclusion
92
+ The route defined for netzke_controller
93
+ netzke.html.erb-layout is not needed anymore, so not produced by the generator
94
+ Now compliant with Rails' forgery protection
95
+
96
+ v0.1.3
97
+ Generators fixed
98
+
99
+ v0.1.2
100
+ Fixed the bug with <widget>_class_definition returning empty string on sequential loading.
101
+
102
+ v0.1.1.1
103
+ Meta: moving from GitHub to RubyForge
104
+
105
+ v0.1.1
106
+ Inter-widget dependencies code reworked
107
+ JS-class code generation code slightly reworked
108
+
1
109
  v0.1.0.2
2
110
  Meta: fix outdated Manifest
3
111
 
data/LICENSE CHANGED
@@ -1,20 +1,3 @@
1
- Copyright (c) 2008 Sergei Kozlov
1
+ Copyright (c) 2008-2009 Sergei Kozlov
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3
+ GNU GPL license v3
@@ -0,0 +1,50 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.rdoc
5
+ Rakefile
6
+ TODO
7
+ autotest/discover.rb
8
+ generators/netzke_core/USAGE
9
+ generators/netzke_core/netzke_core_generator.rb
10
+ generators/netzke_core/templates/create_netzke_preferences.rb
11
+ init.rb
12
+ install.rb
13
+ javascripts/core.js
14
+ lib/app/controllers/netzke_controller.rb
15
+ lib/app/models/netzke_preference.rb
16
+ lib/netzke-core.rb
17
+ lib/netzke/action_view_ext.rb
18
+ lib/netzke/base.rb
19
+ lib/netzke/base_js.rb
20
+ lib/netzke/controller_extensions.rb
21
+ lib/netzke/core_ext.rb
22
+ lib/netzke/feedback_ghost.rb
23
+ lib/netzke/routing.rb
24
+ netzke-core.gemspec
25
+ stylesheets/core.css
26
+ tasks/netzke_core_tasks.rake
27
+ test/app_root/app/controllers/application_controller.rb
28
+ test/app_root/app/models/role.rb
29
+ test/app_root/app/models/user.rb
30
+ test/app_root/config/boot.rb
31
+ test/app_root/config/database.yml
32
+ test/app_root/config/environment.rb
33
+ test/app_root/config/environments/in_memory.rb
34
+ test/app_root/config/environments/mysql.rb
35
+ test/app_root/config/environments/postgresql.rb
36
+ test/app_root/config/environments/sqlite.rb
37
+ test/app_root/config/environments/sqlite3.rb
38
+ test/app_root/config/routes.rb
39
+ test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb
40
+ test/app_root/db/migrate/20090423214303_create_roles.rb
41
+ test/app_root/db/migrate/20090423222114_create_users.rb
42
+ test/app_root/lib/console_with_fixtures.rb
43
+ test/app_root/script/console
44
+ test/fixtures/roles.yml
45
+ test/fixtures/users.yml
46
+ test/test_helper.rb
47
+ test/unit/core_ext_test.rb
48
+ test/unit/netzke_core_test.rb
49
+ test/unit/netzke_preference_test.rb
50
+ uninstall.rb
@@ -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
@@ -2,10 +2,9 @@ require 'echoe'
2
2
 
3
3
  Echoe.new("netzke-core") do |p|
4
4
  p.author = "Sergei Kozlov"
5
- p.email = "sergei@writelesscode.com"
5
+ p.email = "sergei@playcode.nl"
6
6
  p.summary = "Build ExtJS/Rails widgets with minimum effort"
7
- p.url = "http://writelesscode.com"
8
- # p.runtime_dependencies = ["searchlogic >=1.6.2"]
7
+ p.url = "http://playcode.nl"
9
8
  p.development_dependencies = []
10
9
  p.test_pattern = 'test/**/*_test.rb'
11
10
  p.retain_gemspec = true
data/TODO ADDED
@@ -0,0 +1,2 @@
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
@@ -0,0 +1,3 @@
1
+ Autotest.add_discovery do
2
+ "rails"
3
+ end
@@ -2,12 +2,12 @@
2
2
  class NetzkeCoreGenerator < Rails::Generator::Base
3
3
  def manifest
4
4
  record do |m|
5
- m.directory "public/javascripts/netzke"
6
- # m.file 'netzke.js', "public/javascripts/netzke/netzke.js"
7
- m.file 'netzke.html.erb', "app/views/layouts/netzke.html.erb"
8
- m.migration_template 'create_netzke_preferences.rb', "db/migrate", {:migration_file_name => "create_netzke_preferences"}
9
- # FIXME: how do we avoid getting the same migration IDs?
10
- m.migration_template 'create_netzke_layouts.rb', "db/migrate", {:migration_file_name => "create_netzke_layouts"}
5
+ # FIXME: how do we avoid getting the same migration timestamps?
6
+ # Work-around
7
+ time = Time.now.utc.strftime("%Y%m%d%H%M%S")
8
+ m.directory 'db/migrate'
9
+ # m.file 'create_netzke_layouts.rb', "db/migrate/#{time}_create_netzke_layouts.rb"
10
+ m.file 'create_netzke_preferences.rb', "db/migrate/#{time.to_i+1}_create_netzke_preferences.rb"
11
11
  end
12
12
  end
13
13
  end
@@ -3,10 +3,10 @@ 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
- t.string :custom_field
9
+ t.string :widget_name
10
10
 
11
11
  t.timestamps
12
12
  end
@@ -1,124 +1,487 @@
1
+ /*
2
+ This file gets loaded along with the rest of Ext library at the initial load
3
+ */
4
+
1
5
  Ext.BLANK_IMAGE_URL = "/extjs/resources/images/default/s.gif";
2
- Ext.componentCache = {};
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 = {};
9
+
10
+ Ext.QuickTips.init(); // seems obligatory in Ext v2.2.1, otherwise Ext.Component#destroy() stops working properly
11
+
12
+ // To comply with Rails' forgery protection
13
+ Ext.Ajax.extraParams = {
14
+ authenticity_token : Ext.authenticityToken
15
+ };
16
+
17
+ // Type detection functions
18
+ Netzke.isObject = function(o) {
19
+ return (o != null && typeof o == "object" && o.constructor.toString() == Object.toString());
20
+ }
21
+
22
+ // Some Ruby-ish String extensions
23
+ // from http://code.google.com/p/inflection-js/
24
+ String.prototype.camelize=function(lowFirstLetter)
25
+ {
26
+ var str=this.toLowerCase();
27
+ var str_path=str.split('/');
28
+ for(var i=0;i<str_path.length;i++)
29
+ {
30
+ var str_arr=str_path[i].split('_');
31
+ var initX=((lowFirstLetter&&i+1==str_path.length)?(1):(0));
32
+ for(var x=initX;x<str_arr.length;x++)
33
+ str_arr[x]=str_arr[x].charAt(0).toUpperCase()+str_arr[x].substring(1);
34
+ str_path[i]=str_arr.join('');
35
+ }
36
+ str=str_path.join('::');
37
+ return str;
38
+ };
3
39
 
4
- Ext.namespace('Ext.netzke');
40
+ String.prototype.capitalize=function()
41
+ {
42
+ var str=this.toLowerCase();
43
+ str=str.substring(0,1).toUpperCase()+str.substring(1);
44
+ return str;
45
+ };
5
46
 
6
- // helper method to do multiple Ext.apply's
7
- Ext.chainApply = function(objectArray){
8
- var res = {};
9
- Ext.each(objectArray, function(obj){Ext.apply(res, obj)});
10
- return res;
47
+ String.prototype.humanize=function(lowFirstLetter)
48
+ {
49
+ var str=this.toLowerCase();
50
+ str=str.replace(new RegExp('_id','g'),'');
51
+ str=str.replace(new RegExp('_','g'),' ');
52
+ if(!lowFirstLetter)str=str.capitalize();
53
+ return str;
11
54
  };
12
55
 
13
- // implementation of totalProperty, successProperty and root configuration options for ArrayReader
56
+ // Implementation of totalProperty, successProperty and root configuration options for ArrayReader
14
57
  Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
15
- readRecords : function(o){
16
- var sid = this.meta ? this.meta.id : null;
17
- var recordType = this.recordType, fields = recordType.prototype.fields;
18
- var records = [];
19
- // console.info(this.meta);
20
- var root = o[this.meta.root] || o, totalRecords = o[this.meta.totalProperty], success = o[this.meta.successProperty];
21
- for(var i = 0; i < root.length; i++){
22
- var n = root[i];
23
- var values = {};
24
- var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
25
- for(var j = 0, jlen = fields.length; j < jlen; j++){
26
- var f = fields.items[j];
27
- var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
28
- var v = n[k] !== undefined ? n[k] : f.defaultValue;
29
- v = f.convert(v, n);
30
- values[f.name] = v;
31
- }
32
- var record = new recordType(values, id);
33
- record.json = n;
34
- records[records.length] = record;
35
- }
36
- return {
37
- records : records,
38
- totalRecords : totalRecords,
39
- success : success
40
- };
58
+ readRecords : function(o){
59
+ var sid = this.meta ? this.meta.id : null;
60
+ var recordType = this.recordType, fields = recordType.prototype.fields;
61
+ var records = [];
62
+ var root = o[this.meta.root] || o, totalRecords = o[this.meta.totalProperty], success = o[this.meta.successProperty];
63
+ for(var i = 0; i < root.length; i++){
64
+ var n = root[i];
65
+ var values = {};
66
+ var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
67
+ for(var j = 0, jlen = fields.length; j < jlen; j++){
68
+ var f = fields.items[j];
69
+ var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
70
+ var v = n[k] !== undefined ? n[k] : f.defaultValue;
71
+ v = f.convert(v, n);
72
+ values[f.name] = v;
73
+ }
74
+ var record = new recordType(values, id);
75
+ record.json = n;
76
+ records[records.length] = record;
41
77
  }
78
+ return {
79
+ records : records,
80
+ totalRecords : totalRecords,
81
+ success : success
82
+ };
83
+ }
42
84
  });
43
85
 
44
- // Methods common to all widget classes
86
+ // Properties/methods common to all widget classes
45
87
  Ext.widgetMixIn = {
46
- widgetInit:function(config){
47
- this.app = Ext.getCmp('application');
48
- if (config.tools) Ext.each(config.tools, function(i){i.on.click = this[i.on.click].createDelegate(this)}, this);
49
- if (config.actions) Ext.each(config.actions, function(i){i.handler = this[i.handler].createDelegate(this);}, this);
50
- },
51
-
52
- setEvents: function(){
53
- this.on('beforedestroy', function(){
54
- // clean-up menus
55
- if (this.app && !!this.app.unhostMenus) {
56
- // alert('beforedestroy');
57
- this.app.unhostMenus(this)
58
- }
59
- }, this);
60
-
61
- this.on('render', this.onWidgetLoad, this);
62
- },
63
-
64
- feedback:function(msg){
65
- if (this.app && !!this.app.showFeedback) {
66
- this.app.showFeedback(msg)
67
- } else {
68
- // there's no application to show the feedback - so, we do it ourselves
69
- if (typeof msg == 'string'){
70
- alert(msg)
71
- } else {
72
- var compoundResponse = ""
73
- Ext.each(msg, function(m){
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
+ if (this.callbackHash[params.id]) {
130
+ this.callbackHash[params.id].call(params.scope || this, this.getChildWidget(params.id));
131
+ delete this.callbackHash[params.id];
132
+ }
133
+ }
134
+ },
135
+
136
+ /*
137
+ Returns the parent widget
138
+ */
139
+ getParent: function(){
140
+ // simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
141
+ var idSplit = this.id.split("__");
142
+ idSplit.pop();
143
+ var parentId = idSplit.join("__");
144
+
145
+ return parentId === "" ? null : Ext.getCmp(parentId);
146
+ },
147
+
148
+ /*
149
+ Reloads current widget (calls the parent to reload it as its aggregatee)
150
+ */
151
+ reload : function(){
152
+ var parent = this.getParent();
153
+ if (parent) {
154
+ parent.loadAggregatee({id:this.localId(parent), container:this.ownerCt.id});
155
+ } else {
156
+ window.location.reload();
157
+ }
158
+ },
159
+
160
+ /*
161
+ Gets id in the context of provided parent.
162
+ For example, the widgets "properties", being a child of "books" has global id "books__properties",
163
+ which *is* its widegt's real id. This methods, with the instance of "books" passed as parameter,
164
+ returns "properties".
165
+ */
166
+ localId : function(parent){
167
+ return this.id.replace(parent.id + "__", "");
168
+ },
169
+
170
+ /*
171
+ Instantiates and inserts a widget into a container with layout 'fit'.
172
+ Arg: an JS object with the following keys:
173
+ - id: id of the receiving container
174
+ - config: configuration of the widget to be instantiated and inserted into the container
175
+ */
176
+ renderWidgetInContainer : function(params){
177
+ var cont = Ext.getCmp(params.container);
178
+ cont.instantiateChild(params.config);
179
+ },
180
+
181
+ /*
182
+ Reconfigures the widget
183
+ */
184
+ reconfigure: function(config){
185
+ this.ownerCt.instantiateChild(config)
186
+ },
187
+
188
+ /*
189
+ Evaluates CSS
190
+ */
191
+ css : function(code){
192
+ var linkTag = document.createElement('style');
193
+ linkTag.type = 'text/css';
194
+ linkTag.innerHTML = code;
195
+ document.body.appendChild(linkTag);
196
+ },
197
+
198
+ /*
199
+ Evaluates JS
200
+ */
201
+ js : function(code){
202
+ eval(code);
203
+ },
204
+
205
+ /*
206
+ Executes a bunch of methods. This method is called almost every time a communication to the server takes place.
207
+ Thus the server side of a widget can provide any set of commands to its client side.
208
+ Args:
209
+ - instructions: array of methods, in the order of execution.
210
+ Each item is an object in one of the following 2 formats:
211
+ 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
212
+ 2) {widget:widget_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child widget
213
+
214
+ Example:
215
+ - [
216
+ // the same as this.feedback("Your order is accepted")
217
+ {feedback: "You order is accepted"},
218
+
219
+ // the same as this.getChildWidget('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}])
220
+ {widget:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] },
221
+
222
+ // ... etc:
223
+ {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}},
224
+ {setColums:[{},{}]},
225
+ {setMenus:[{},{}]},
226
+ ...
227
+ ]
228
+ */
229
+ bulkExecute : function(instructions){
230
+ if (Ext.isArray(instructions)) {
231
+ Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this);
232
+ } else {
233
+ for (var instr in instructions) {
234
+ if (this[instr]) {
235
+ this[instr].apply(this, [instructions[instr]]);
236
+ } else {
237
+ var childWidget = this.getChildWidget(instr);
238
+ if (childWidget) {
239
+ childWidget.bulkExecute(instructions[instr]);
240
+ } else {
241
+ throw "Unknown method or child widget '" + instr +"' in widget '" + this.id + "'"
242
+ }
243
+ }
244
+ }
245
+ }
246
+ },
247
+
248
+ // Get the child widget
249
+ getChildWidget : function(id){
250
+ return id === 'parent' ? this.getParent() : Ext.getCmp(this.id+"__"+id);
251
+ },
252
+
253
+ // Common handler for actions
254
+ actionHandler : function(action){
255
+ // If firing corresponding event doesn't return false, call the handler
256
+ if (this.fireEvent(action.name+'click', action)) {
257
+ this[(action.fn || action.name)](action);
258
+ }
259
+ },
260
+
261
+ // Common handler for tools
262
+ toolActionHandler : function(tool){
263
+ // If firing corresponding event doesn't return false, call the handler
264
+ if (this.fireEvent(tool.id+'click')) {
265
+ this[tool]();
266
+ }
267
+ },
268
+
269
+ // Does the call to the server and processes the response
270
+ callServer : function(intp, params, callback, scope){
271
+ if (!params) params = {};
272
+ Ext.Ajax.request({
273
+ params : params,
274
+ url : this.id + "__" + intp,
275
+ callback : function(options, success, response){
276
+ if (success) {
277
+ // execute commands from server
278
+ this.bulkExecute(Ext.decode(response.responseText));
279
+
280
+ // provade callback if needed
281
+ if (typeof callback == 'function') {
282
+ if (!scope) scope = this;
283
+ callback.apply(scope);
284
+ }
285
+ }
286
+ },
287
+ scope : this
288
+ });
289
+ },
290
+
291
+ /* Parse the bbar and tbar (both Arrays), replacing the strings with the corresponding methods. For example:
292
+ replaceStringsWithActions( ['add', {text:'Menu', menu:['edit', 'delete']}] )
293
+ => [scope.actions['add'], {text:'Menu', menu:[scope.actions['edit'], scope.actions['delete']]}]
294
+ */
295
+ normalizeMenuItems: function(arry, scope){
296
+ var res = []; // new array
297
+ Ext.each(arry, function(o){
298
+ if (typeof o === "string") {
299
+ var camelized = o.camelize(true);
300
+ if (scope.actions[camelized]){
301
+ res.push(scope.actions[camelized]);
302
+ } else {
303
+ // if there's no action with this name, maybe it's a separator or something
304
+ res.push(o);
305
+ }
306
+ } else if (Netzke.isObject(o)) {
307
+ // look inside the objects...
308
+ for (var key in o) {
309
+ if (Ext.isArray(o[key])) {
310
+ // ... and recursively process inner arrays found
311
+ o[key] = this.normalizeMenuItems(o[key], scope);
312
+ }
313
+ }
314
+ res.push(o);
315
+ }
316
+ }, this);
317
+ return res;
318
+ },
319
+
320
+
321
+ // Every Netzke widget
322
+ commonBeforeConstructor : function(config){
323
+ this.actions = {};
324
+
325
+ // Generate methods for api points
326
+ if (!config.api) { config.api = []; }
327
+ config.api.push('load_aggregatee_with_cache'); // all netzke widgets get this API
328
+ Ext.each(config.api, function(intp){
329
+ this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
330
+ }, this);
331
+
332
+ // Create Ext.Actions based on config.actions
333
+ if (config.actions) {
334
+ this.testActions = {};
335
+ for (var name in config.actions) {
336
+ // Create an event for each action (so that higher-level widgets could interfere)
337
+ this.addEvents(name+'click');
338
+
339
+ // Configure the action
340
+ var actionConfig = config.actions[name];
341
+ actionConfig.handler = this.actionHandler.createDelegate(this);
342
+ actionConfig.name = name;
343
+ this.actions[name] = new Ext.Action(actionConfig);
344
+ }
345
+
346
+ config.bbar = config.bbar && this.normalizeMenuItems(config.bbar, this);
347
+ config.tbar = config.tbar && this.normalizeMenuItems(config.tbar, this);
348
+ config.menu = config.menu && this.normalizeMenuItems(config.menu, this);
349
+ config.contextMenu = config.contextMenu && this.normalizeMenuItems(config.contextMenu, this);
350
+
351
+ // TODO: need to rethink this action related stuff
352
+ config.actions = this.actions;
353
+
354
+ }
355
+
356
+ // Normalize tools
357
+ if (config.tools) {
358
+ var normTools = [];
359
+ Ext.each(config.tools, function(tool){
360
+ // Create an event for each action (so that higher-level widgets could interfere)
361
+ this.addEvents(tool.id+'click');
362
+
363
+ var handler = this.toolActionHandler.createDelegate(this, [tool]);
364
+ normTools.push({id : tool, handler : handler, scope : this});
365
+ }, this);
366
+ config.tools = normTools;
367
+ }
368
+
369
+ // Set title
370
+ if (!config.title) config.title = config.id.humanize();
371
+ },
372
+
373
+ // At this moment component is fully initializied
374
+ commonAfterConstructor : function(config){
375
+ // From everywhere accessible FeedbackGhost
376
+ this.feedbackGhost = Ext.getCmp('feedback_ghost');
377
+
378
+ // Add the menus
379
+ if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);}
380
+
381
+ // generic events
382
+ this.addEvents(
383
+ 'widgetload' // fired when a child is dynamically loaded
384
+ );
385
+
386
+ // Cleaning up on destroy
387
+ this.on('beforedestroy', function(){
388
+ this.cleanUpMenu();
389
+ }, this);
390
+
391
+ this.callbackHash = {};
392
+
393
+ if (this.afterConstructor) this.afterConstructor(config);
394
+ },
395
+
396
+ feedback:function(msg){
397
+ if (this.initialConfig && this.initialConfig.quiet) {
398
+ return false;
399
+ }
400
+
401
+ if (this.feedbackGhost) {
402
+ this.feedbackGhost.showFeedback(msg);
403
+ } else {
404
+ // there's no application to show the feedback - so, we do it ourselves
405
+ if (typeof msg == 'string'){
406
+ alert(msg);
407
+ } else {
408
+ var compoundResponse = "";
409
+ Ext.each(msg, function(m){
74
410
  compoundResponse += m.msg + "\n"
75
- })
76
- if (compoundResponse != "") alert(compoundResponse);
77
- }
78
- };
79
- },
80
-
81
- addMenus:function(menus){
82
- if (this.app && !!this.app.hostMenu) {
83
- Ext.each(menus, function(menu){this.app.hostMenu(menu, this)}, this)
84
- }
85
- },
86
-
87
- onWidgetLoad:Ext.emptyFn // gets overridden
411
+ });
412
+ if (compoundResponse != "") {
413
+ alert(compoundResponse);
414
+ }
415
+ }
416
+ }
417
+ },
418
+
419
+ addMenu : function(menu, owner){
420
+ if (!owner) {
421
+ owner = this;
422
+ }
423
+
424
+ if (!!this.hostMenu) {
425
+ this.hostMenu(menu, owner);
426
+ } else {
427
+ if (this.ownerWidget) {
428
+ this.ownerWidget.addMenu(menu, owner);
429
+ }
430
+ }
431
+ },
432
+
433
+ cleanUpMenu : function(owner){
434
+ if (!owner) {
435
+ owner = this;
436
+ }
437
+
438
+ if (!!this.unhostMenu) {
439
+ this.unhostMenu(owner);
440
+ } else {
441
+ if (this.ownerWidget) {
442
+ this.ownerWidget.cleanUpMenu(owner);
443
+ }
444
+ }
445
+ },
446
+
447
+ onWidgetLoad:Ext.emptyFn // gets overridden
88
448
  };
89
449
 
90
- // Make Panel with layout 'fit' capable to dynamically load widgets
91
- Ext.override(Ext.Panel, {
92
- getWidget: function(){
93
- return this.items.get(0)
94
- },
95
-
96
- loadWidget: function(url, params){
97
- if (!params) params = {}
98
-
99
- this.remove(this.getWidget()); // first delete previous widget
100
-
101
- // we will let the server know which components we have cached
102
- var cachedComponentNames = [];
103
- for (name in Ext.componentCache) {
104
- cachedComponentNames.push(name);
105
- }
106
-
107
- this.disable(); // to visually emphasize loading
108
-
109
- Ext.Ajax.request(
110
- {url:url, params:Ext.apply(params, {components_cache:Ext.encode(cachedComponentNames)}), script:false, callback:function(panel, success, response){
111
- var response = Ext.decode(response.responseText);
112
- if (response['classDefinition']) eval(response['classDefinition']); // evaluate widget's class if it was sent
113
-
114
- response.config.parent = this // we might want to know the parent panel in advance (e.g. to know its size)
115
- var instance = new Ext.componentCache[response.config.widgetClassName](response.config)
116
-
117
- this.add(instance);
118
- this.doLayout();
119
- this.enable();
120
- }, scope:this}
121
- )
122
-
123
- }
124
- });
450
+ // Netzke extensions for Ext.Container
451
+ Ext.override(Ext.Container, {
452
+ /**
453
+ Get Netzke widget that this Ext.Container is part of (*not* the parent widget, for which call getParent)
454
+ It searches up the Ext.Container hierarchy until it finds a Container that has isNetzke property set to true
455
+ (or until it reaches the top).
456
+ */
457
+ getOwnerWidget : function(){
458
+ if (this.initialConfig.isNetzke) {
459
+ return this;
460
+ } else {
461
+ if (this.ownerCt){
462
+ return this.ownerCt.getOwnerWidget()
463
+ } else {
464
+ return null
465
+ }
466
+ }
467
+ },
468
+
469
+ // Get the widget that we are hosting
470
+ getWidget: function(){
471
+ 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
472
+ },
473
+
474
+ removeChild : function(){
475
+ this.remove(this.getWidget());
476
+ },
477
+
478
+ instantiateChild : function(config){
479
+ this.remove(this.getWidget()); // first delete previous widget
480
+
481
+ if (!config) return false; // simply remove current widget if null is passed
482
+
483
+ var instance = new Ext.netzke.cache[config.widgetClassName](config);
484
+ this.add(instance);
485
+ this.doLayout();
486
+ }
487
+ });