netzke-core 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/.autotest +1 -1
  2. data/.gitignore +4 -2
  3. data/CHANGELOG.rdoc +37 -15
  4. data/README.rdoc +93 -28
  5. data/Rakefile +5 -7
  6. data/TODO +24 -3
  7. data/app/controllers/netzke_controller.rb +70 -0
  8. data/app/models/netzke_preference.rb +170 -0
  9. data/autotest/discover.rb +2 -3
  10. data/features/actions.feature +13 -0
  11. data/features/basic.feature +7 -0
  12. data/features/client-server.feature +15 -0
  13. data/features/complex_component.feature +18 -0
  14. data/features/component.feature +13 -0
  15. data/features/component_loader.feature +31 -0
  16. data/features/composition.feature +32 -0
  17. data/features/custom_css.feature +17 -0
  18. data/features/file_inclusion.feature +13 -0
  19. data/features/inheritance.feature +19 -0
  20. data/features/persistence.feature +16 -0
  21. data/features/scopes.feature +17 -0
  22. data/features/step_definitions/custom_css_steps.rb +7 -0
  23. data/features/step_definitions/generic_steps.rb +15 -0
  24. data/features/step_definitions/web_steps.rb +219 -0
  25. data/features/support/env.rb +62 -0
  26. data/features/support/paths.rb +45 -0
  27. data/generators/netzke_core/USAGE +3 -3
  28. data/generators/netzke_core/netzke_core_generator.rb +8 -0
  29. data/install.rb +1 -1
  30. data/javascripts/core.js +542 -499
  31. data/lib/generators/migration_helper.rb +32 -0
  32. data/lib/generators/netzke/USAGE +9 -0
  33. data/lib/generators/netzke/core_generator.rb +24 -0
  34. data/lib/netzke/actions.rb +102 -0
  35. data/lib/netzke/base.rb +77 -529
  36. data/lib/netzke/composition.rb +251 -0
  37. data/lib/netzke/configuration.rb +134 -0
  38. data/lib/netzke/core/masquerading.rb +34 -0
  39. data/lib/netzke/core/session.rb +30 -0
  40. data/lib/netzke/core/version.rb +11 -0
  41. data/lib/netzke/core.rb +53 -0
  42. data/lib/netzke/core_ext/array.rb +30 -0
  43. data/lib/netzke/core_ext/hash.rb +84 -0
  44. data/lib/netzke/core_ext/string.rb +26 -0
  45. data/lib/netzke/core_ext/symbol.rb +13 -0
  46. data/lib/netzke/core_ext/time_with_zone.rb +7 -0
  47. data/lib/netzke/core_ext.rb +5 -141
  48. data/lib/netzke/embedding.rb +21 -0
  49. data/lib/netzke/ext_component.rb +25 -0
  50. data/lib/netzke/javascript.rb +248 -0
  51. data/lib/netzke/persistence.rb +118 -0
  52. data/lib/netzke/rails/action_view_ext.rb +103 -0
  53. data/lib/netzke/{controller_extensions.rb → rails/controller_extensions.rb} +7 -2
  54. data/lib/netzke/rails/routes.rb +7 -0
  55. data/lib/netzke/services.rb +68 -0
  56. data/lib/netzke/session.rb +35 -0
  57. data/lib/netzke/stylesheets.rb +52 -0
  58. data/lib/netzke-core.rb +28 -29
  59. data/netzke-core.gemspec +253 -0
  60. data/spec/component/actions_spec.rb +94 -0
  61. data/spec/component/base_spec.rb +25 -0
  62. data/spec/component/composition_spec.rb +132 -0
  63. data/spec/component/javascript_spec.rb +15 -0
  64. data/spec/core_ext_spec.rb +16 -0
  65. data/spec/spec.opt +2 -0
  66. data/spec/spec_helper.rb +35 -0
  67. data/{test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb → templates/core/create_netzke_preferences.rb} +4 -4
  68. data/test/rails_app/.gitignore +4 -0
  69. data/test/rails_app/Gemfile +36 -0
  70. data/test/rails_app/Gemfile.lock +137 -0
  71. data/test/rails_app/README +15 -0
  72. data/test/rails_app/Rakefile +7 -0
  73. data/test/rails_app/app/components/border_layout_panel.rb +4 -0
  74. data/test/rails_app/app/components/component_loader.rb +59 -0
  75. data/test/rails_app/app/components/component_with_actions.rb +58 -0
  76. data/test/rails_app/app/components/component_with_custom_css.rb +8 -0
  77. data/test/rails_app/app/components/component_with_included_js.rb +16 -0
  78. data/test/rails_app/app/components/component_with_session_persistence.rb +25 -0
  79. data/test/rails_app/app/components/custom.css +3 -0
  80. data/test/rails_app/app/components/deprecated/server_caller.rb +20 -0
  81. data/test/rails_app/app/components/extended_component_with_actions.rb +5 -0
  82. data/test/rails_app/app/components/extended_server_caller.rb +18 -0
  83. data/test/rails_app/app/components/included.js +5 -0
  84. data/test/rails_app/app/components/kinda_complex_component/basic_stuff.rb +35 -0
  85. data/test/rails_app/app/components/kinda_complex_component/extra_stuff.rb +16 -0
  86. data/test/rails_app/app/components/kinda_complex_component.rb +7 -0
  87. data/test/rails_app/app/components/loader_of_component_with_custom_css.rb +15 -0
  88. data/test/rails_app/app/components/scoped_components/deep_scoped_components/some_deep_scoped_component.rb +7 -0
  89. data/test/rails_app/app/components/scoped_components/extended_scoped_component.rb +5 -0
  90. data/test/rails_app/app/components/scoped_components/some_scoped_component.rb +5 -0
  91. data/test/rails_app/app/components/server_caller.rb +21 -0
  92. data/test/rails_app/app/components/simple_component.rb +5 -0
  93. data/test/rails_app/app/components/simple_tab_panel.rb +27 -0
  94. data/test/rails_app/app/components/simple_window.rb +3 -0
  95. data/test/rails_app/app/components/some_composite.rb +65 -0
  96. data/test/rails_app/app/components/some_ext_component.rb +8 -0
  97. data/test/{app_root → rails_app}/app/controllers/application_controller.rb +1 -0
  98. data/test/rails_app/app/controllers/components_controller.rb +12 -0
  99. data/test/rails_app/app/controllers/multiple_components_controller.rb +2 -0
  100. data/test/rails_app/app/controllers/welcome_controller.rb +5 -0
  101. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  102. data/test/rails_app/app/views/layouts/application.html.erb +13 -0
  103. data/test/rails_app/app/views/multiple_components/set_one.html.erb +3 -0
  104. data/test/rails_app/config/application.rb +45 -0
  105. data/test/rails_app/config/boot.rb +13 -0
  106. data/test/rails_app/config/database.yml +22 -0
  107. data/test/rails_app/config/environment.rb +6 -0
  108. data/test/rails_app/config/environments/development.rb +22 -0
  109. data/test/rails_app/config/environments/production.rb +49 -0
  110. data/test/rails_app/config/environments/test.rb +35 -0
  111. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  112. data/test/rails_app/config/initializers/inflections.rb +10 -0
  113. data/test/rails_app/config/initializers/mime_types.rb +5 -0
  114. data/test/rails_app/config/initializers/netzke.rb +3 -0
  115. data/test/rails_app/config/initializers/secret_token.rb +7 -0
  116. data/test/rails_app/config/initializers/session_store.rb +8 -0
  117. data/test/rails_app/config/locales/en.yml +5 -0
  118. data/test/rails_app/config/routes.rb +64 -0
  119. data/test/rails_app/config.ru +4 -0
  120. data/test/rails_app/db/development_structure.sql +4 -0
  121. data/{generators/netzke_core/templates/create_netzke_preferences.rb → test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb} +1 -3
  122. data/test/rails_app/db/schema.rb +24 -0
  123. data/test/rails_app/db/seeds.rb +7 -0
  124. data/test/{app_root/config/environments/in_memory.rb → rails_app/lib/tasks/.gitkeep} +0 -0
  125. data/test/rails_app/public/404.html +26 -0
  126. data/test/rails_app/public/422.html +26 -0
  127. data/test/rails_app/public/500.html +26 -0
  128. data/test/{app_root/config/environments/mysql.rb → rails_app/public/favicon.ico} +0 -0
  129. data/test/rails_app/public/images/rails.png +0 -0
  130. data/test/rails_app/public/robots.txt +5 -0
  131. data/test/rails_app/script/rails +6 -0
  132. data/test/{app_root/config/environments/postgresql.rb → rails_app/vendor/plugins/.gitkeep} +0 -0
  133. data/test/unit/netzke_core_test.rb +74 -75
  134. data/test/unit/netzke_preference_test.rb +2 -2
  135. metadata +176 -48
  136. data/lib/app/controllers/netzke_controller.rb +0 -46
  137. data/lib/app/models/netzke_preference.rb +0 -171
  138. data/lib/netzke/action_view_ext.rb +0 -81
  139. data/lib/netzke/base_js.rb +0 -339
  140. data/lib/netzke/routing.rb +0 -9
  141. data/test/app_root/app/models/role.rb +0 -3
  142. data/test/app_root/app/models/user.rb +0 -3
  143. data/test/app_root/config/boot.rb +0 -115
  144. data/test/app_root/config/database.yml +0 -31
  145. data/test/app_root/config/environment.rb +0 -14
  146. data/test/app_root/config/environments/sqlite.rb +0 -0
  147. data/test/app_root/config/environments/sqlite3.rb +0 -0
  148. data/test/app_root/config/routes.rb +0 -4
  149. data/test/app_root/db/migrate/20090423214303_create_roles.rb +0 -11
  150. data/test/app_root/db/migrate/20090423222114_create_users.rb +0 -12
  151. data/test/app_root/lib/console_with_fixtures.rb +0 -4
  152. data/test/app_root/script/console +0 -7
data/javascripts/core.js CHANGED
@@ -1,34 +1,43 @@
1
1
  /*
2
2
  This file gets loaded along with the rest of Ext library at the initial load
3
+ At this time the following constants have been set by Rails:
4
+
5
+ Netzke.RelativeUrlRoot - set to ActionController::Base.config.relative_url_root
6
+ Netzke.RelativeExtUrl - URL to ext files
3
7
  */
4
8
 
9
+ // Initial stuff
10
+ Ext.BLANK_IMAGE_URL = Netzke.RelativeExtUrl + "/resources/images/default/s.gif";
11
+ Ext.ns('Ext.netzke'); // namespace for extensions that depend on Ext
12
+
13
+ Netzke.deprecationWarning = function(msg){
14
+ if (typeof console == 'undefined') {
15
+ // no console defined
16
+ } else {
17
+ console.info("Netzke: " + msg);
18
+ }
19
+ };
20
+
5
21
  // Check Ext JS version
6
22
  (function(){
7
- var requiredExtVersion = "3.2.1";
23
+ var requiredExtVersion = "3.3.0";
8
24
  var currentExtVersion = Ext.version;
9
25
  if (requiredExtVersion !== currentExtVersion) {
10
- alert("Netzke needs Ext " + requiredExtVersion + ". You have " + currentExtVersion + ".");
26
+ Netzke.deprecationWarning("Netzke needs Ext " + requiredExtVersion + ". You have " + currentExtVersion + ".");
11
27
  }
12
28
  })();
13
29
 
14
- // Initial stuff
15
- Ext.BLANK_IMAGE_URL = "/extjs/resources/images/default/s.gif";
16
- Ext.ns('Ext.netzke'); // namespace for extensions that depend on Ext
17
- Ext.ns('Netzke'); // Netzke namespace
18
- Ext.ns('Netzke.page'); // namespace for all widget instantces on the page
19
- Ext.ns('Netzke.pre'); // namespace for pre-built, static JS classes
20
- Ext.ns('Netzke.classes'); // namespace for finished netzke widgets
30
+ Ext.ns('Netzke.page'); // namespace for all component instantces on the page
31
+ Ext.ns('Netzke.classes'); // namespace for all component classes
32
+
33
+ // Because of Netzke's double-underscore notation, Ext.TabPanel should have a different id-delimiter (yes, this should be in netzke-core)
34
+ Ext.TabPanel.prototype.idDelimiter = "___";
21
35
 
22
36
  Ext.QuickTips.init();
23
37
 
24
38
  // We don't want no state managment by default, thank you!
25
39
  Ext.state.Provider.prototype.set = function(){};
26
40
 
27
- // Type detection functions
28
- Netzke.isObject = function(o) {
29
- return (o != null && typeof o == "object" && o.constructor.toString() == Object.toString());
30
- }
31
-
32
41
  // Some Ruby-ish String extensions
33
42
  // from http://code.google.com/p/inflection-js/
34
43
  String.prototype.camelize=function(lowFirstLetter)
@@ -63,548 +72,582 @@ String.prototype.humanize=function(lowFirstLetter)
63
72
  return str;
64
73
  };
65
74
 
66
- // Implementation of totalProperty, successProperty and root configuration options for ArrayReader
67
- Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
68
- readRecords : function(o){
69
- var sid = this.meta ? this.meta.id : null;
70
- var recordType = this.recordType, fields = recordType.prototype.fields;
71
- var records = [];
72
- var root = o[this.meta.root] || o, totalRecords = o[this.meta.totalProperty], success = o[this.meta.successProperty];
73
- for(var i = 0; i < root.length; i++){
74
- var n = root[i];
75
- var values = {};
76
- var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
77
- for(var j = 0, jlen = fields.length; j < jlen; j++){
78
- var f = fields.items[j];
79
- var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
80
- var v = n[k] !== undefined ? n[k] : f.defaultValue;
81
- v = f.convert(v, n);
82
- values[f.name] = v;
83
- }
84
- var record = new recordType(values, id);
85
- record.json = n;
86
- records[records.length] = record;
87
- }
88
- return {
89
- records : records,
90
- totalRecords : totalRecords,
91
- success : success
92
- };
93
- }
94
- });
75
+ // This one is borrowed from prototype.js
76
+ String.prototype.underscore = function() {
77
+ return this.replace(/::/g, '/')
78
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
79
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
80
+ .replace(/-/g, '_')
81
+ .toLowerCase();
82
+ }
95
83
 
96
- // Properties/methods common to all widget classes
97
- Ext.widgetMixIn = {
98
- height: 400,
99
- // width: 800,
100
- border: false,
101
- isNetzke: true, // to distinguish Netzke components from regular Ext components
102
- latestResult: {}, // latest result returned from the server via an API call
103
-
104
- /*
105
- Loads aggregatee into a container.
106
- */
107
- loadAggregatee: function(params){
108
- // 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.
109
- var apiParams = Ext.apply({id: params.id, container: params.container}, params.params);
110
-
111
- // build the cached widgets list to send it to the server
112
- var cachedWidgetNames = "";
113
-
114
- // recursive function that checks the properties of the caller ("this") and returns the list of those that look like constructor, i.e. have an "xtype" property themselves
115
- var classesList = function(pref){
116
- var res = [];
117
- for (name in this) {
118
- if (this[name].xtype) {
119
- res.push(pref + name);
120
- this[name].classesList = classesList; // define the same function on each property on the fly
121
- res = res.concat(this[name].classesList(pref + name + ".")); // ... and call it, providing our name along with the scope
122
- }
123
- }
124
- return res;
125
- };
126
-
127
- // assign this function to Netzke.classes and call it
128
- Netzke.classes.classesList = classesList;
129
- var cl = Netzke.classes.classesList("");
84
+ // Usefull when using mixins
85
+ Netzke.aliasMethodChain = function(klass, method, feature) {
86
+ klass[method + "Without" + feature.capitalize()] = klass[method];
87
+ klass[method] = klass[method + "With" + feature.capitalize()];
88
+ }
130
89
 
131
- // join the classes into a coma-separated list
132
- var cache = "";
133
- Ext.each(cl, function(c){cache += c + ",";});
134
-
135
- // for (name in Netzke.classes) {
136
- // cachedWidgetNames += name + ",";
137
- // }
138
- // apiParams.cache = cachedWidgetNames;
139
- apiParams.cache = cache;
90
+ // Properties/methods common to all component classes
91
+ Netzke.componentMixin = function(receiver){
92
+ return {
93
+ height: 400,
94
+ border: false,
95
+ isNetzke: true, // to distinguish Netzke components from regular Ext components
96
+ latestResult: {}, // latest result returned from the server via an API call
140
97
 
141
- // remember the passed callback for the future
142
- if (params.callback) {
143
- this.callbackHash[params.id] = params.callback; // per loaded widget, as there may be simultaneous calls
144
- }
98
+ /*
99
+ Overriding the constructor to only apply an "alias method chain" to initComponent
100
+ */
101
+ constructor : function(config){
102
+ Netzke.aliasMethodChain(this, "initComponent", "netzke");
103
+ receiver.superclass.constructor.call(this, config);
104
+ },
145
105
 
146
- // visually disable the container while the widget is being loaded
147
- // Ext.getCmp(params.container).disable();
106
+ /* initComponent common for all Netzke components */
107
+ initComponentWithNetzke : function(){
108
+ this.normalizeActions();
109
+
110
+ this.detectActions(this);
111
+
112
+ this.detectComponents(this.items);
113
+
114
+ this.normalizeTools();
115
+
116
+ this.processEndpoints();
117
+
118
+ // This is where the references to different callback functions will be stored
119
+ this.callbackHash = {};
120
+
121
+ // This is where we store the information about components that are currently being loaded with this.loadComponent()
122
+ this.componentsBeingLoaded = {};
148
123
 
149
- if (params.container) Ext.getCmp(params.container).removeChild(); // remove the old widget if the container is specified
150
-
151
- // do the remote API call
152
- this.loadAggregateeWithCache(apiParams);
153
- },
154
-
155
- /*
156
- Called by the server as callback about loaded widget
157
- */
158
- widgetLoaded : function(params){
159
- if (this.fireEvent('widgetload')) {
160
- // Enable the container after the widget is succesfully loaded
161
- // this.getChildWidget(params.id).ownerCt.enable();
124
+ // Set title
125
+ if (this.mode === "config"){
126
+ if (!this.title) {
127
+ this.title = '[' + this.id + ']';
128
+ } else {
129
+ this.title = this.title + ' [' + this.id + ']';
130
+ }
131
+ } else {
132
+ if (!this.title) {
133
+ this.title = this.id.humanize();
134
+ }
135
+ }
136
+
137
+ // From everywhere accessible FeedbackGhost
138
+ this.feedbackGhost = new Netzke.FeedbackGhost();
139
+
140
+ // Call the original initComponent
141
+ this.initComponentWithoutNetzke();
142
+ },
143
+
144
+ /*
145
+ Dynamically creates methods for api points, so that we could later call them like: this.myEndpointMethod()
146
+ */
147
+ processEndpoints : function(){
148
+ var endpoints = this.endpoints || [];
149
+ endpoints.push('deliver_component'); // all Netzke components get this endpoint
150
+ Ext.each(endpoints, function(intp){
151
+ this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
152
+ }, this);
153
+ },
154
+
155
+ normalizeTools: function() {
156
+ if (this.tools) {
157
+ var normTools = [];
158
+ Ext.each(this.tools, function(tool){
159
+ // Create an event for each action (so that higher-level components could interfere)
160
+ this.addEvents(tool.id+'click');
162
161
 
163
- // provide the callback to that widget that was loading the child, passing the child itself
164
- var callbackFn = this.callbackHash[params.id.camelize(true)];
165
- if (callbackFn) {
166
- callbackFn.call(params.scope || this, this.getChildWidget(params.id));
167
- delete this.callbackHash[params.id.camelize(true)];
162
+ var handler = this.toolActionHandler.createDelegate(this, [tool]);
163
+ normTools.push({id : tool, handler : handler, scope : this});
164
+ }, this);
165
+ this.tools = normTools;
168
166
  }
169
- }
170
- },
171
-
172
- /*
173
- Returns the parent widget
174
- */
175
- getParent: function(){
176
- // simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
177
- var idSplit = this.id.split("__");
178
- idSplit.pop();
179
- var parentId = idSplit.join("__");
180
-
181
- return parentId === "" ? null : Ext.getCmp(parentId);
182
- },
183
-
184
- /*
185
- Reloads current widget (calls the parent to reload it as its aggregatee)
186
- */
187
- reload : function(){
188
- var parent = this.getParent();
189
- if (parent) {
190
- parent.loadAggregatee({id:this.localId(parent), container:this.ownerCt.id});
191
- } else {
192
- window.location.reload();
193
- }
194
- },
195
-
196
- /*
197
- Gets id in the context of provided parent.
198
- For example, the widgets "properties", being a child of "books" has global id "books__properties",
199
- which *is* its widegt's real id. This methods, with the instance of "books" passed as parameter,
200
- returns "properties".
201
- */
202
- localId : function(parent){
203
- return this.id.replace(parent.id + "__", "");
204
- },
205
-
206
- /*
207
- Instantiates and inserts a widget into a container with layout 'fit'.
208
- Arg: an JS object with the following keys:
209
- - id: id of the receiving container
210
- - config: configuration of the widget to be instantiated and inserted into the container
211
- */
212
- renderWidgetInContainer : function(params){
213
- var cont = Ext.getCmp(params.container);
214
- if (cont) {
215
- cont.instantiateChild(params.config);
216
- } else {
217
- this.instantiateChild(params.config);
218
- }
219
- },
220
-
221
- /*
222
- Reconfigures the widget
223
- */
224
- reconfigure: function(config){
225
- this.ownerCt.instantiateChild(config)
226
- },
227
-
228
- /*
229
- Evaluates CSS
230
- */
231
- css : function(code){
232
- var linkTag = document.createElement('style');
233
- linkTag.type = 'text/css';
234
- linkTag.innerHTML = code;
235
- document.body.appendChild(linkTag);
236
- },
237
-
238
- /*
239
- Evaluates JS
240
- */
241
- js : function(code){
242
- eval(code);
243
- },
244
-
245
- /*
246
- Executes a bunch of methods. This method is called almost every time a communication to the server takes place.
247
- Thus the server side of a widget can provide any set of commands to its client side.
248
- Args:
249
- - instructions: array of methods, in the order of execution.
250
- Each item is an object in one of the following 2 formats:
251
- 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
252
- 2) {widget:widget_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child widget
253
-
254
- Example:
255
- - [
256
- // the same as this.feedback("Your order is accepted")
257
- {feedback: "You order is accepted"},
258
-
259
- // the same as this.getChildWidget('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}])
260
- {widget:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] },
261
-
262
- // ... etc:
263
- {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}},
264
- {setColums:[{},{}]},
265
- {setMenus:[{},{}]},
266
- ...
267
- ]
268
- */
269
- bulkExecute : function(instructions){
270
- if (Ext.isArray(instructions)) {
271
- Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this);
272
- } else {
273
- for (var instr in instructions) {
274
- if (this[instr]) {
275
- this[instr].apply(this, [instructions[instr]]);
276
- } else {
277
- var childWidget = this.getChildWidget(instr);
278
- if (childWidget) {
279
- childWidget.bulkExecute(instructions[instr]);
280
- } else {
281
- throw "Netzke: Unknown method or child widget '" + instr +"' in widget '" + this.id + "'"
282
- }
167
+ },
168
+
169
+ /*
170
+ Replaces actions configs with Ext.Action instances, assigning default handler to them
171
+ */
172
+ normalizeActions : function(){
173
+ var normActions = {};
174
+ for (var name in this.actions) {
175
+ // Create an event for each action (so that higher-level components could interfere)
176
+ this.addEvents(name+'click');
177
+
178
+ // Configure the action
179
+ var actionConfig = this.actions[name];
180
+ actionConfig.customHandler = actionConfig.handler;
181
+ actionConfig.handler = this.actionHandler.createDelegate(this); // handler common for all actions
182
+ actionConfig.name = name;
183
+ normActions[name] = new Ext.Action(actionConfig);
184
+ }
185
+ delete(this.actions);
186
+ this.actions = normActions;
187
+ },
188
+
189
+ /*
190
+ Detects action configs in the passed object, and replaces them with instances of Ext.Action created by normalizeActions().
191
+ This detects action in arbitrary level of nesting, which means you can put any other components in your toolbar, and inside of them specify menus/items or even toolbars.
192
+ */
193
+ detectActions: function(o){
194
+ if (Ext.isObject(o)) {
195
+ if ((typeof o.handler === 'string') && Ext.isFunction(this[o.handler.camelize(true)])) {
196
+ // This button config has a handler specified as string - replace it with reference to a real function if it exists
197
+ o.handler = this[o.handler.camelize(true)].createDelegate(this);
283
198
  }
199
+ // TODO: this should be configurable!
200
+ Ext.each(["bbar", "tbar", "fbar", "menu", "items", "contextMenu", "buttons"], function(key){
201
+ if (o[key]) {
202
+ var items = [].concat(o[key]); // we need to do it in order to esure that this instance has a separate bbar/tbar/etc, NOT shared via class' prototype
203
+ delete(o[key]);
204
+ o[key] = items;
205
+ this.detectActions(o[key]);
206
+ }
207
+ }, this);
208
+ } else if (Ext.isArray(o)) {
209
+ var a = o;
210
+ Ext.each(a, function(el, i){
211
+ if (Ext.isObject(el)) {
212
+ if (el.action) {
213
+ if (!this.actions[el.action.camelize(true)]) throw "Netzke: action '"+el.action+"' not defined";
214
+ a[i] = this.actions[el.action.camelize(true)];
215
+ delete(el);
216
+ } else {
217
+ this.detectActions(el);
218
+ }
219
+ }
220
+ }, this);
221
+ }
222
+ },
223
+
224
+ /*
225
+ Detects component placeholders in the passed object (typically, "items"),
226
+ and merges them with the corresponding config from this.components.
227
+ This way it becomes ready to be instantiated properly by Ext.
228
+ */
229
+ detectComponents: function(o){
230
+ if (Ext.isObject(o)) {
231
+ if (o.items) this.detectComponents(o.items);
232
+ } else if (Ext.isArray(o)) {
233
+ var a = o;
234
+ Ext.each(a, function(el, i){
235
+ if (el.component) {
236
+ a[i] = Ext.apply(this.components[el.component.camelize(true)], el);
237
+ delete a[i].component;
238
+ } else if (el.items) this.detectComponents(el.items);
239
+ }, this);
240
+ }
241
+ },
242
+
243
+ /*
244
+ Loads a component. Config options:
245
+ 'name' (required) - the name of the child component to load
246
+ 'container' - the id of a panel with the 'fit' layout where the loaded component will be instantiated
247
+ 'callback' - function that gets called after the component is loaded. It receives the component's instance as parameter.
248
+ 'scope' - scope for the callback.
249
+ */
250
+ loadComponent: function(params){
251
+ if (params.id) {
252
+ params.name = params.id;
253
+ Netzke.deprecationWarning("Using 'id' in loadComponent is deprecated. Use 'name' instead.");
284
254
  }
285
- }
286
- },
287
-
288
- // Get the child widget
289
- getChildWidget : function(id){
290
- if (id === "") {return this};
291
-
292
- var split = id.split("__");
293
- if (split[0] === 'parent') {
294
- split.shift();
295
- var childInParentScope = split.join("__");
296
- return this.getParent().getChildWidget(childInParentScope);
297
- } else {
298
- return Ext.getCmp(this.id+"__"+id);
299
- }
300
- },
301
-
302
- // Common handler for all widget's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
303
- actionHandler : function(comp){
304
- var actionName = comp.name;
305
- // If firing corresponding event doesn't return false, call the handler
306
- if (this.fireEvent(actionName+'click', comp)) {
307
- var action = this.actions[actionName];
308
- var customHandler = action.initialConfig.customHandler;
309
- var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
310
- if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
311
255
 
312
- // call the handler passing it the triggering component
313
- this[methodName](comp);
314
- }
315
- },
256
+ params.name = params.name.underscore();
316
257
 
317
- // Common handler for tools
318
- toolActionHandler : function(tool){
319
- // If firing corresponding event doesn't return false, call the handler
320
- if (this.fireEvent(tool.id+'click')) {
321
- var methodName = "on"+tool.camelize();
322
- if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"}
323
- this[methodName]();
324
- }
325
- },
258
+ // params that will be provided for the server API call (deliver_component); all what's passed in params.params is merged in. This way we exclude from sending along such things as :scope, :callback, etc.
259
+ var serverParams = params.params || {};
260
+ serverParams.name = params.name;
261
+
262
+ // Build the list of already loaded ("cached") classes
263
+ serverParams.cache = [];
264
+
265
+ for (var klass in Netzke.classes) {
266
+ serverParams.cache.push(klass);
267
+ }
268
+
269
+ serverParams.cache = serverParams.cache.join();
326
270
 
327
- // Returns API url based on provided API point
328
- buildApiUrl: function(apip){
329
- return "/netzke/" + this.id + "__" + apip;
330
- },
271
+ var storedConfig = this.componentsBeingLoaded[params.name] = {};
331
272
 
332
- // Does the call to the server and processes the response
333
- callServer : function(intp, params, callback, scope){
334
- if (!params) params = {};
335
- Ext.Ajax.request({
336
- params: params,
337
- url: this.buildApiUrl(intp),
338
- callback: function(options, success, response){
339
- if (success) {
340
- // execute commands from server
341
- this.bulkExecute(Ext.decode(response.responseText));
342
-
343
- // provide callback if needed
344
- if (typeof callback == 'function') {
345
- if (!scope) scope = this;
346
- callback.apply(scope, [this.latestResult]);
347
- }
348
- }
349
- },
350
- scope : this
351
- });
352
- },
273
+ // Remember where the loaded component should be inserted into
274
+ if (params.container) {
275
+ storedConfig.container = params.container;
276
+ }
353
277
 
354
- setResult: function(result) {
355
- this.latestResult = result;
356
- },
278
+ // remember the passed callback for the future (per loaded component, as there may be simultaneous ongoing calls)
279
+ if (params.callback) {
280
+ storedConfig.callback = params.callback;
281
+ storedConfig.scope = params.scope;
282
+ // this.callbackHash[params.name.underscore()] = params.callback;
283
+ }
357
284
 
358
- /* Normalize an array of abstracted button configs into an array of Ext button configs according to the following rules:
359
- - 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)
360
- - if the element is an object, then:
361
- -- 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.
362
- -- 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
363
- */
364
- normalizeMenuItems: function(arry, scope){
365
- var res = []; // new array
366
- Ext.each(arry, function(o){
367
- if (typeof o === "string") {
368
- var camelized = o.camelize(true);
369
- if (scope.actions[camelized]){
370
- res.push(scope.actions[camelized]);
285
+ // remove the old component if the container is specified
286
+ if (params.container) Ext.getCmp(params.container).removeChild();
287
+
288
+ // do the remote API call
289
+ this.deliverComponent(serverParams);
290
+ },
291
+
292
+ /*
293
+ Called by the server after we ask him to load a component
294
+ */
295
+ componentDelivered : function(config){
296
+ if (this.fireEvent('componentload'), config) {
297
+ var storedConfig = this.componentsBeingLoaded[config.name] || {};
298
+ delete this.componentsBeingLoaded[config.name];
299
+
300
+ var componentInstance;
301
+
302
+ if (storedConfig.container) {
303
+ var container = Ext.getCmp(storedConfig.container);
304
+ componentInstance = container.instantiateChild(config);
371
305
  } else {
372
- // if there's no action with this name, maybe it's a separator or text or whatever
373
- res.push(o);
306
+ componentInstance = this.instantiateChild(config);
374
307
  }
375
- } else if (Netzke.isObject(o)) {
376
- // look inside the objects...
377
- if (o.menu) {
378
- // ... and recursively process nested menus
379
- o.menu = this.normalizeMenuItems(o.menu, scope);
380
- } else if (o.handler && Ext.isFunction(scope[o.handler.camelize(true)])) {
381
- // This button config has a handler specified as string - replace it with reference to a real function if it exists
382
- o.handler = scope[o.handler.camelize(true)];
308
+
309
+ if (storedConfig.callback) {
310
+ storedConfig.callback.call(storedConfig.scope || this, componentInstance);
383
311
  }
384
- res.push(o);
385
312
  }
386
- }, this);
387
-
388
- delete arry;
389
-
390
- return res;
391
- },
392
-
393
- locateMenus : function(o) {
394
- var keyWords = ["bbar", "tbar", "fbar"];
395
- if (Ext.isObject(o)) {
396
- Ext.each(keyWords, function(key){
397
- if (o[key]) {
398
- o[key] = this.normalizeMenuItems(o[key], this);
399
- };
400
- }, this);
401
-
402
- for (var key in o) {
403
- if (keyWords.indexOf(key) == -1) {
404
- this.locateMenus(o[key]);
313
+ },
314
+
315
+ /*
316
+ Instantiates and inserts a component into a container with layout 'fit'.
317
+ Arg: an JS object with the following keys:
318
+ - id: id of the receiving container
319
+ - config: configuration of the component to be instantiated and inserted into the container
320
+ */
321
+ // renderComponentInContainer : function(params){
322
+ // var cont = Ext.getCmp(params.container);
323
+ // if (cont) {
324
+ // cont.instantiateChild(params.config);
325
+ // } else {
326
+ // this.instantiateChild(params.config);
327
+ // }
328
+ // },
329
+
330
+ /*
331
+ Returns the parent component
332
+ */
333
+ getParent: function(){
334
+ // simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
335
+ var idSplit = this.id.split("__");
336
+ idSplit.pop();
337
+ var parentId = idSplit.join("__");
338
+
339
+ return parentId === "" ? null : Ext.getCmp(parentId);
340
+ },
341
+
342
+ /*
343
+ Reloads current component (calls the parent to reload it as its component)
344
+ */
345
+ reload : function(){
346
+ var parent = this.getParent();
347
+ if (parent) {
348
+ parent.loadComponent({id:this.localId(parent), container:this.ownerCt.id});
349
+ } else {
350
+ window.location.reload();
351
+ }
352
+ },
353
+
354
+ /*
355
+ Gets id in the context of provided parent.
356
+ For example, the components "properties", being a child of "books" has global id "books__properties",
357
+ which *is* its widegt's real id. This methods, with the instance of "books" passed as parameter,
358
+ returns "properties".
359
+ */
360
+ localId : function(parent){
361
+ return this.id.replace(parent.id + "__", "");
362
+ },
363
+
364
+ /*
365
+ Reconfigures the component
366
+ */
367
+ reconfigure: function(config){
368
+ this.ownerCt.instantiateChild(config)
369
+ },
370
+
371
+ /*
372
+ Evaluates CSS
373
+ */
374
+ evalCss : function(code){
375
+ var linkTag = document.createElement('style');
376
+ linkTag.type = 'text/css';
377
+ linkTag.innerHTML = code;
378
+ document.body.appendChild(linkTag);
379
+ },
380
+
381
+ /*
382
+ Evaluates JS
383
+ */
384
+ evalJs : function(code){
385
+ eval(code);
386
+ },
387
+
388
+ /*
389
+ Executes a bunch of methods. This method is called almost every time a communication to the server takes place.
390
+ Thus the server side of a component can provide any set of commands to its client side.
391
+ Args:
392
+ - instructions: array of methods, in the order of execution.
393
+ Each item is an object in one of the following 2 formats:
394
+ 1) {method1:args1, method2:args2}, where methodN is a name of a public method of this component; these methods are called in no particular order
395
+ 2) {component:component_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child component
396
+
397
+ Example:
398
+ - [
399
+ // the same as this.feedback("Your order is accepted")
400
+ {feedback: "You order is accepted"},
401
+
402
+ // the same as this.getChildComponent('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}])
403
+ {component:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] },
404
+
405
+ // ... etc:
406
+ {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}},
407
+ {setColums:[{},{}]},
408
+ {setMenus:[{},{}]},
409
+ ...
410
+ ]
411
+ */
412
+ bulkExecute : function(instructions){
413
+ if (Ext.isArray(instructions)) {
414
+ Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this);
415
+ } else {
416
+ for (var instr in instructions) {
417
+ if (Ext.isFunction(this[instr])) {
418
+ this[instr].apply(this, [instructions[instr]]); // execute the method
419
+ } else {
420
+ var childComponent = this.getChildComponent(instr);
421
+ if (childComponent) {
422
+ childComponent.bulkExecute(instructions[instr]);
423
+ } else {
424
+ throw "Netzke: Unknown method or child component '" + instr +"' in component '" + this.id + "'"
425
+ }
426
+ }
405
427
  }
406
428
  }
407
- } else if (Ext.isArray(o)) {
408
- Ext.each(o, function(el){
409
- this.locateMenus(el);
410
- }, this);
411
- }
429
+ },
430
+
431
+ // Get the child component
432
+ getChildComponent : function(id){
433
+ if (id === "") {return this};
434
+ id = id.underscore();
435
+ var split = id.split("__");
436
+ if (split[0] === 'parent') {
437
+ split.shift();
438
+ var childInParentScope = split.join("__");
439
+ return this.getParent().getChildComponent(childInParentScope);
440
+ } else {
441
+ return Ext.getCmp(this.id+"__"+id);
442
+ }
443
+ },
444
+
445
+ // Common handler for all component's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
446
+ // actionHandler : function(comp){
447
+ // var actionName = comp.name;
448
+ // // If firing corresponding event doesn't return false, call the handler
449
+ // if (this.fireEvent(actionName+'click', comp)) {
450
+ // var action = this.actions[actionName];
451
+ // var customHandler = action.initialConfig.customHandler;
452
+ // var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
453
+ // if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
454
+ //
455
+ // // call the handler passing it the triggering component
456
+ // this[methodName](comp);
457
+ // }
458
+ // },
459
+ //
460
+ // // Common handler for tools
461
+ // toolActionHandler : function(tool){
462
+ // // If firing corresponding event doesn't return false, call the handler
463
+ // if (this.fireEvent(tool.id+'click')) {
464
+ // var methodName = "on"+tool.camelize();
465
+ // if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"}
466
+ // this[methodName]();
467
+ // }
468
+ // },
469
+
470
+ // Returns API url based on provided API point
471
+ buildApiUrl: function(endpoint){
472
+ Netzke.deprecationWarning("buildApiUrl() is deprecated. Use endpointUrl() first");
473
+ return this.endpointUrl(endpoint);
474
+ },
412
475
 
413
- },
476
+ endpointUrl: function(endpoint){
477
+ return Netzke.RelativeUrlRoot + "/netzke/" + this.id + "__" + endpoint;
478
+ },
479
+
480
+ // Does the call to the server and processes the response
481
+ callServer : function(intp, params, callback, scope){
482
+ if (!params) params = {};
483
+ Ext.Ajax.request({
484
+ params: params,
485
+ url: this.endpointUrl(intp),
486
+ callback: function(options, success, response){
487
+ if (success && response.responseText) {
488
+ // execute commands from server
489
+ this.bulkExecute(Ext.decode(response.responseText));
490
+
491
+ // provide callback if needed
492
+ if (typeof callback == 'function') {
493
+ if (!scope) scope = this;
494
+ callback.apply(scope, [this.latestResult]);
495
+ }
496
+ }
497
+ },
498
+ scope : this
499
+ });
500
+ },
414
501
 
415
- // Code run before calling Ext's constructor - normalizing config to provide Netzke additional functionality
416
- commonBeforeConstructor : function(config){
417
- // Dynamically create methods for api points, so that we could later call them like: this.myApiMethod()
418
- var apiPoints = config.netzkeApi || [];
419
- apiPoints.push('load_aggregatee_with_cache'); // all netzke widgets get this API point
420
- Ext.each(apiPoints, function(intp){
421
- this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
422
- }, this);
423
-
424
- // This will contain Ext.Action instances
425
- this.actions = {};
426
-
427
- // Create Ext.Action instances based on config.actions
428
- if (config.actions) {
429
- for (var name in config.actions) {
430
- // Create an event for each action (so that higher-level widgets could interfere)
431
- this.addEvents(name+'click');
502
+ setResult: function(result) {
503
+ this.latestResult = result;
504
+ },
432
505
 
433
- // Configure the action
434
- var actionConfig = config.actions[name];
435
- actionConfig.customHandler = actionConfig.handler || actionConfig.fn; //DEPRECATED: .fn is kept for backward compatibility, preferred way is to specify handler
436
- actionConfig.handler = this.actionHandler.createDelegate(this); // ! this is the "wrapper-handler", which is common for all actions!
437
- actionConfig.name = name;
438
- this.actions[name] = new Ext.Action(actionConfig);
439
- }
440
-
441
- // TODO: need to rethink this action related stuff
442
- config.actions = this.actions;
443
- }
444
-
445
- this.locateMenus(config);
446
-
447
- // config.bbar = config.bbar && this.normalizeMenuItems(config.bbar, this);
448
- // config.tbar = config.tbar && this.normalizeMenuItems(config.tbar, this);
449
- // config.fbar = config.fbar && this.normalizeMenuItems(config.fbar, this);
450
- config.contextMenu = config.contextMenu && this.normalizeMenuItems(config.contextMenu, this);
451
-
452
- config.menu = config.menu && this.normalizeMenuItems(config.menu, this);
453
-
454
- // Normalize tools
455
- if (config.tools) {
456
- var normTools = [];
457
- Ext.each(config.tools, function(tool){
458
- // Create an event for each action (so that higher-level widgets could interfere)
459
- this.addEvents(tool.id+'click');
460
-
461
- var handler = this.toolActionHandler.createDelegate(this, [tool]);
462
- normTools.push({id : tool, handler : handler, scope : this});
463
- }, this);
464
- config.tools = normTools;
465
- }
466
-
467
- // Set title
468
- if (config.mode === "config"){
469
- if (!config.title) {
470
- config.title = '[' + config.id + ']';
471
- } else {
472
- config.title = config.title + ' [' + config.id + ']';
473
- }
474
- } else {
475
- if (!config.title) {
476
- config.title = config.id.humanize();
477
- }
478
- }
479
- },
506
+ // At this moment component is fully initializied
507
+ commonAfterConstructor : function(config){
480
508
 
481
- // At this moment component is fully initializied
482
- commonAfterConstructor : function(config){
483
- // From everywhere accessible FeedbackGhost
484
- this.feedbackGhost = new Netzke.FeedbackGhost();
509
+ // Add the menus
510
+ if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);}
485
511
 
486
- // Add the menus
487
- if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);}
512
+ // generic events
513
+ this.addEvents(
514
+ 'componentload' // fired when a child is dynamically loaded
515
+ );
488
516
 
489
- // generic events
490
- this.addEvents(
491
- 'widgetload' // fired when a child is dynamically loaded
492
- );
517
+ // Cleaning up on destroy
518
+ this.on('beforedestroy', function(){
519
+ this.cleanUpMenu();
520
+ }, this);
493
521
 
494
- // Cleaning up on destroy
495
- this.on('beforedestroy', function(){
496
- this.cleanUpMenu();
497
- }, this);
498
-
499
- this.callbackHash = {};
522
+ this.callbackHash = {};
500
523
 
501
- if (this.afterConstructor) this.afterConstructor(config);
502
- },
524
+ if (this.afterConstructor) this.afterConstructor(config);
525
+ },
503
526
 
504
- feedback:function(msg){
505
- if (this.initialConfig && this.initialConfig.quiet) {
506
- return false;
507
- }
508
-
509
- if (this.feedbackGhost) {
510
- this.feedbackGhost.showFeedback(msg);
511
- } else {
512
- // there's no application to show the feedback - so, we do it ourselves
513
- if (typeof msg == 'string'){
514
- alert(msg);
527
+ feedback:function(msg){
528
+ if (this.initialConfig && this.initialConfig.quiet) {
529
+ return false;
530
+ }
531
+
532
+ if (this.feedbackGhost) {
533
+ this.feedbackGhost.showFeedback(msg);
515
534
  } else {
516
- var compoundResponse = "";
517
- Ext.each(msg, function(m){
518
- compoundResponse += m.msg + "\n"
519
- });
520
- if (compoundResponse != "") {
521
- alert(compoundResponse);
535
+ // there's no application to show the feedback - so, we do it ourselves
536
+ if (typeof msg == 'string'){
537
+ alert(msg);
538
+ } else {
539
+ var compoundResponse = "";
540
+ Ext.each(msg, function(m){
541
+ compoundResponse += m.msg + "\n"
542
+ });
543
+ if (compoundResponse != "") {
544
+ alert(compoundResponse);
545
+ }
522
546
  }
523
547
  }
524
- }
525
- },
526
-
527
- addMenu : function(menu, owner){
528
- if (!owner) {
529
- owner = this;
530
- }
531
-
532
- if (!!this.hostMenu) {
533
- this.hostMenu(menu, owner);
534
- } else {
535
- if (this.ownerWidget) {
536
- this.ownerWidget.addMenu(menu, owner);
548
+ },
549
+
550
+ // addMenu : function(menu, owner){
551
+ // if (!owner) {
552
+ // owner = this;
553
+ // }
554
+ //
555
+ // if (!!this.hostMenu) {
556
+ // this.hostMenu(menu, owner);
557
+ // } else {
558
+ // if (this.ownerComponent) {
559
+ // this.ownerComponent.addMenu(menu, owner);
560
+ // }
561
+ // }
562
+ // },
563
+ //
564
+ // cleanUpMenu : function(owner){
565
+ // if (!owner) {
566
+ // owner = this;
567
+ // }
568
+ //
569
+ // if (!!this.unhostMenu) {
570
+ // this.unhostMenu(owner);
571
+ // } else {
572
+ // if (this.ownerComponent) {
573
+ // this.ownerComponent.cleanUpMenu(owner);
574
+ // }
575
+ // }
576
+ // },
577
+
578
+ // Common handler for all component's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
579
+ actionHandler : function(comp){
580
+ var actionName = comp.name;
581
+ // If firing corresponding event doesn't return false, call the handler
582
+ if (this.fireEvent(actionName+'click', comp)) {
583
+ var action = this.actions[actionName];
584
+ var customHandler = action.initialConfig.customHandler;
585
+ var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
586
+ if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
587
+
588
+ // call the handler passing it the triggering component
589
+ this[methodName](comp);
537
590
  }
538
- }
539
- },
540
-
541
- cleanUpMenu : function(owner){
542
- if (!owner) {
543
- owner = this;
544
- }
591
+ },
592
+
593
+ // Common handler for tools
594
+ toolActionHandler : function(tool){
595
+ // If firing corresponding event doesn't return false, call the handler
596
+ if (this.fireEvent(tool.id+'click')) {
597
+ var methodName = "on"+tool.camelize();
598
+ if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"}
599
+ this[methodName]();
600
+ }
601
+ },
545
602
 
546
- if (!!this.unhostMenu) {
547
- this.unhostMenu(owner);
603
+ onComponentLoad:Ext.emptyFn // gets overridden
604
+ }
605
+ }
606
+
607
+
608
+ // Netzke extensions for Ext.Container
609
+ Ext.override(Ext.Container, {
610
+ // Instantiates an component by its config. If it appears to be a window, shows it instead of adding as item.
611
+ // TODO: there must be a method to just instantiate a component, but not to add/show it instantly.
612
+ instantiateChild : function(config){
613
+ var instance = Ext.create(config);
614
+ if (instance.isXType("window")) {
615
+ instance.show();
548
616
  } else {
549
- if (this.ownerWidget) {
550
- this.ownerWidget.cleanUpMenu(owner);
551
- }
617
+ this.remove(this.getNetzkeComponent()); // first delete previous component
618
+ this.add(instance);
619
+ this.doLayout();
552
620
  }
621
+ return instance;
553
622
  },
554
-
555
- onWidgetLoad:Ext.emptyFn // gets overridden
556
- };
557
623
 
558
- // Netzke extensions for Ext.Container
559
- Ext.override(Ext.Container, {
560
624
  /**
561
- Get Netzke widget that this Ext.Container is part of (*not* the parent widget, for which call getParent)
625
+ Get Netzke component that this Ext.Container is part of (*not* the parent component, for which call getParent)
562
626
  It searches up the Ext.Container hierarchy until it finds a Container that has isNetzke property set to true
563
627
  (or until it reaches the top).
564
628
  */
565
- getOwnerWidget : function(){
629
+ getOwnerComponent : function(){
566
630
  if (this.initialConfig.isNetzke) {
567
631
  return this;
568
632
  } else {
569
633
  if (this.ownerCt){
570
- return this.ownerCt.getOwnerWidget()
634
+ return this.ownerCt.getOwnerComponent()
571
635
  } else {
572
636
  return null
573
637
  }
574
638
  }
575
639
  },
576
640
 
577
- // Get the widget that we are hosting
578
- getWidget: function(){
579
- 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
641
+ // Get the component that we are hosting
642
+ getNetzkeComponent: function(){
643
+ return this.items ? this.items.first() : null; // need this check in case when the container is not yet rendered, like an inactive tab in the TabPanel
580
644
  },
581
645
 
582
646
  // Remove the child
583
647
  removeChild : function(){
584
- this.remove(this.getWidget());
585
- },
586
-
587
- // Given a scoped class name, returns the actual class, e.g.: "Netzke.GridPanel" => Netzke.classes.Netzke.GridPanel
588
- classifyScopedName : function(n){
589
- var klass = Netzke.classes;
590
- Ext.each(n.split("."), function(s){
591
- klass = klass[s];
592
- });
593
- return klass;
594
- },
595
-
596
- // Instantiates an aggregatee by its config. If it appears to be a window, shows it instead of adding as item.
597
- instantiateChild : function(config){
598
- var klass = this.classifyScopedName(config.scopedClassName);
599
- var instance = new klass(config);
600
- if (instance.isXType("netzkewindow")) {
601
- instance.show();
602
- } else {
603
- this.remove(this.getWidget()); // first delete previous widget
604
- this.add(instance);
605
- this.doLayout();
606
- }
648
+ this.remove(this.getNetzkeComponent());
607
649
  }
650
+
608
651
  });
609
652
 
610
653
 
@@ -636,5 +679,5 @@ Ext.apply(Netzke.FeedbackGhost.prototype, {
636
679
  } else {
637
680
  showBox(msg);
638
681
  }
639
- }
682
+ }
640
683
  });