netzke-core 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG.rdoc +19 -0
  2. data/README.markdown +43 -0
  3. data/TODO +1 -5
  4. data/app/controllers/netzke_controller.rb +47 -15
  5. data/config/database.yml +2 -0
  6. data/features/component_loader.feature +6 -1
  7. data/features/composition.feature +2 -0
  8. data/features/js_include.feature +18 -0
  9. data/features/nested_views.feature +9 -0
  10. data/features/persistence.feature +6 -4
  11. data/features/support/paths.rb +3 -0
  12. data/javascripts/core.js +166 -519
  13. data/javascripts/ext.js +355 -0
  14. data/javascripts/touch.js +47 -0
  15. data/lib/netzke/actions.rb +31 -38
  16. data/lib/netzke/base.rb +48 -6
  17. data/lib/netzke/composition.rb +52 -63
  18. data/lib/netzke/configuration.rb +6 -2
  19. data/lib/netzke/core/version.rb +2 -2
  20. data/lib/netzke/core.rb +22 -15
  21. data/lib/netzke/javascript/scopes.rb +39 -0
  22. data/lib/netzke/javascript.rb +145 -114
  23. data/lib/netzke/railz/action_view_ext/ext.rb +59 -0
  24. data/lib/netzke/railz/action_view_ext/touch.rb +50 -0
  25. data/lib/netzke/railz/action_view_ext.rb +86 -0
  26. data/lib/netzke/railz/controller_extensions.rb +33 -0
  27. data/lib/netzke/{rails → railz}/routes.rb +0 -0
  28. data/lib/netzke/railz.rb +3 -0
  29. data/lib/netzke/session.rb +18 -3
  30. data/lib/netzke/state.rb +42 -15
  31. data/lib/netzke/stylesheets.rb +23 -8
  32. data/lib/netzke-core.rb +23 -16
  33. data/netzke-core.gemspec +52 -10
  34. data/spec/component/base_spec.rb +11 -0
  35. data/spec/component/javascript_spec.rb +3 -2
  36. data/spec/component/state_spec.rb +18 -0
  37. data/spec/spec_helper.rb +1 -1
  38. data/test/rails_app/Gemfile +3 -2
  39. data/test/rails_app/Gemfile.lock +73 -71
  40. data/test/rails_app/app/components/component_loader.rb +39 -4
  41. data/test/rails_app/app/components/{custom.css → component_with_custom_css/stylesheets/custom.css} +0 -0
  42. data/test/rails_app/app/components/component_with_custom_css.rb +2 -2
  43. data/test/rails_app/app/components/component_with_js_mixin/javascripts/extra_one.js +2 -0
  44. data/test/rails_app/app/components/component_with_js_mixin/javascripts/extra_two.js +2 -0
  45. data/test/rails_app/app/components/component_with_js_mixin/javascripts/method_set_one.js +6 -0
  46. data/test/rails_app/app/components/component_with_js_mixin/javascripts/method_set_two.js +5 -0
  47. data/test/rails_app/app/components/component_with_js_mixin.rb +8 -0
  48. data/test/rails_app/app/components/component_with_session_persistence.rb +10 -3
  49. data/test/rails_app/app/components/extended_component_with_js_mixin/javascripts/some_method_set.js +5 -0
  50. data/test/rails_app/app/components/extended_component_with_js_mixin.rb +7 -0
  51. data/test/rails_app/app/components/hello_world_component.rb +31 -0
  52. data/test/rails_app/app/components/server_caller.rb +1 -1
  53. data/test/rails_app/app/components/simple_panel.rb +2 -0
  54. data/test/rails_app/app/components/touch/hello_world_component.rb +25 -0
  55. data/test/rails_app/app/components/touch/server_caller.rb +28 -0
  56. data/test/rails_app/app/components/touch/simple_carousel.rb +17 -0
  57. data/test/rails_app/app/controllers/components_controller.rb +6 -1
  58. data/test/rails_app/app/controllers/touch_controller.rb +6 -0
  59. data/test/rails_app/app/helpers/touch_helper.rb +2 -0
  60. data/test/rails_app/app/views/components/panel_with_autoload.html.erb +2 -0
  61. data/test/rails_app/app/views/components/some_tab_panel.html.erb +11 -0
  62. data/test/rails_app/app/views/layouts/nested.html.erb +5 -0
  63. data/test/rails_app/app/views/layouts/touch.html.erb +13 -0
  64. data/test/rails_app/config/initializers/netzke.rb +1 -1
  65. data/test/rails_app/config/locales/en.yml +7 -1
  66. data/test/rails_app/config/routes.rb +10 -1
  67. data/test/rails_app/db/migrate/20110110132720_create_netzke_component_states.rb +20 -0
  68. data/test/rails_app/db/schema.rb +14 -1
  69. data/test/rails_app/spec/controllers/touch_controller_spec.rb +5 -0
  70. data/test/rails_app/spec/helpers/touch_helper_spec.rb +15 -0
  71. data/test/unit/netzke_core_test.rb +2 -6
  72. metadata +53 -11
  73. data/README.rdoc +0 -136
  74. data/lib/netzke/rails/action_view_ext.rb +0 -103
  75. data/lib/netzke/rails/controller_extensions.rb +0 -31
  76. data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,22 @@
1
+ = v0.6.5 - 2011-01-14
2
+ * enhancements
3
+ * Various fixes for IE
4
+ * Support for Sencha Touch
5
+ * An endpoint can now "call" JavaScript functions that accept multiple parameters, by specifying an array, e.g.:
6
+ {:some_js_function => [arg1, arg2]}
7
+ * New API: +js_mixin+ method to "mixin" JavaScript objects from external files (see RDocs).
8
+ * New JS class +componentLoadMask+ property to configure a mask when a component gets dynamically loaded with +loadComponent+. Accepts the same configuration as Ext.LoadMask.
9
+ * +js_include+ and +css_include+ accept both symbols and strings, where strings would contain full paths to the included file, whereas symbols get expanded to full paths following simple conventions (see RDocs for details).
10
+ * Make some of +Netzke::Core+ setup happen earlier in the loading process, so that we can safely use it while defining components.
11
+ * Performance improvements by memoizing +Base.constantize_class_name+.
12
+ * I18n for actions, see +Netzke::Actions+.
13
+
14
+ * bug fix
15
+ * The "componentload" event now gets fired after a component is dynamically loaded. The handler receives the instance of the loaded component.
16
+ * Feedback does not insert a new div every time being called
17
+ * JS class caching was broken for name-scoped classes
18
+ * When a component was dynamically loaded into a hidden container, it wasn't shown when the container got shown next time
19
+
1
20
  = v0.6.4 - 2010-11-05
2
21
  * enhancements
3
22
  * Implemented Netzke.isLoading(), useful for testing
data/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ ## Netzke Core
2
+
3
+ Netzke Core is the bare bones of the [Netzke framework](https://github.com/skozlov/netzke). For pre-built full-featured components (like grids, forms, tab/accordion panels, applications, etc), see [netzke-basepack](http://github.com/skozlov/netzke-basepack) (Ext JS).
4
+
5
+ Netzke Core takes the burden of implementing the following key aspects of the framework:
6
+
7
+ * JavaScript class generation
8
+ * Client-server communication
9
+ * Extendibility of components (with OOP, in both Ruby and JavaScript)
10
+ * Unlimited nesting (composition)
11
+ * Dynamic component loading
12
+ * JavaScript class caching
13
+ * Inclusion of “external” JavaScript CSS files
14
+ * ... and more
15
+
16
+ All this provides for fast, low-traffic, robust, highly maintainable applications.
17
+
18
+ ### Getting started
19
+
20
+ * Follow the simple [installation](https://github.com/skozlov/netzke-core/wiki/Installation) steps.
21
+ * Learn how to build the [Hello World!](https://github.com/skozlov/netzke-core/wiki/Hello-world-extjs) component.
22
+ * Dive into the [documentation](https://github.com/skozlov/netzke/wiki).
23
+ * Get help on the [Google Groups](http://groups.google.com/group/netzke).
24
+
25
+ ### Sencha Touch support
26
+
27
+ Netzke Core has support for Sencha Touch, so you can create components for mobile web apps as easily.
28
+
29
+ * Learn how to build the [Hello World!](https://github.com/skozlov/netzke-core/wiki/Hello-world-touch) Sencha Touch component.
30
+
31
+ ### Testing and playing with Netzke Core
32
+
33
+ Netzke Core is bundled with Cucumber and RSpec tests. If you would like to contribute to the project, you may want to learn how to [run the tests](https://github.com/skozlov/netzke-core/wiki/Automated-testing).
34
+
35
+ Besides, the bundled test application is a convenient [playground](https://github.com/skozlov/netzke-core/wiki/Playground) for those who search to experiment with the framework.
36
+
37
+ ### Useful links
38
+ * [Project website](http://netzke.org).
39
+ * [Documentation](https://github.com/skozlov/netzke/wiki).
40
+ * [Live-demo](http://demo.netzke.org) (with sample code).
41
+ * [Twitter](http://twitter.com/skozlov).
42
+
43
+ *Copyright (c) 2008-2010 Sergei Kozlov, released under the MIT license*
data/TODO CHANGED
@@ -1,11 +1,7 @@
1
- Make plugin-wide javascripts and stylesheets load automatically.
1
+ Caching for netzke_controller-provided JS and CSS.
2
2
  Caching - investigate reusing (fragment?) caching of Rails.
3
3
  Use Ext.Direct in the Netzke controller.
4
4
  Let specify per API point if it will use a GET or a POST request.
5
- Re-work generators
6
-
7
- Some day
8
- * make gzipping generated JS code possible
9
5
 
10
6
 
11
7
  = Ideas that didn't work out
@@ -3,41 +3,73 @@ class NetzkeController < ApplicationController
3
3
  # Collect javascripts and stylesheets from all plugins that registered it in Netzke::Core.javascripts
4
4
  # TODO: caching
5
5
  # caches_action :netzke
6
- def netzke
6
+ def ext
7
7
  respond_to do |format|
8
8
  format.js {
9
9
  res = initial_dynamic_javascript << "\n"
10
- Netzke::Core.javascripts.each do |path|
10
+
11
+ # Core JavaScript
12
+ res << File.new(File.expand_path("../../../javascripts/core.js", __FILE__)).read
13
+ # Ext-specific JavaScript
14
+ res << File.new(File.expand_path("../../../javascripts/ext.js", __FILE__)).read
15
+
16
+ # Pluggable JavaScript (used by other Netzke-powered gems like netzke-basepack)
17
+ Netzke::Core.ext_javascripts.each do |path|
11
18
  f = File.new(path)
12
19
  res << f.read
13
20
  end
14
21
 
15
- # If JS classes are not inserted into the main page, we need to render all the classes needed to load the page that includes us
16
- # (i.e. netzke/netzke.js) here
17
- if !Netzke::Core.javascript_on_main_page
18
- rendered_classes = []
19
- Netzke::Core.session[:netzke_components].each_pair do |k,v|
20
- component = Netzke::Base.instance_by_config(v)
21
- res << component.js_missing_code(rendered_classes.map(&:name))
22
- rendered_classes += component.dependency_classes
23
- rendered_classes.uniq!
24
- end
22
+ render :text => defined?(::Rails) && ::Rails.env.production? ? res.strip_js_comments : res
23
+ }
24
+
25
+ format.css {
26
+ res = File.new(File.expand_path("../../../stylesheets/core.css", __FILE__)).read
27
+
28
+ # Pluggable stylesheets (may be used by other Netzke-powered gems like netzke-basepack)
29
+ Netzke::Core.ext_stylesheets.each do |path|
30
+ f = File.new(path)
31
+ res << f.read
32
+ end
33
+
34
+ render :text => res
35
+ }
36
+ end
37
+ end
38
+
39
+ def touch
40
+ respond_to do |format|
41
+ format.js {
42
+ res = initial_dynamic_javascript << "\n"
43
+
44
+ # Core JavaScript
45
+ res << File.new(File.expand_path("../../../javascripts/core.js", __FILE__)).read
46
+ # Touch-specific JavaScript
47
+ res << File.new(File.expand_path("../../../javascripts/touch.js", __FILE__)).read
48
+
49
+ # Pluggable JavaScript (may be used by other Netzke-powered gems like netzke-basepack)
50
+ Netzke::Core.touch_javascripts.each do |path|
51
+ f = File.new(path)
52
+ res << f.read
25
53
  end
26
54
 
27
55
  render :text => defined?(::Rails) && ::Rails.env.production? ? res.strip_js_comments : res
28
56
  }
29
57
 
30
58
  format.css {
31
- res = ""
32
- Netzke::Core.stylesheets.each do |path|
59
+ res = File.new(File.expand_path("../../../stylesheets/core.css", __FILE__)).read
60
+
61
+ # Pluggable stylesheets (may be used by other Netzke-powered gems like netzke-basepack)
62
+ Netzke::Core.touch_stylesheets.each do |path|
33
63
  f = File.new(path)
34
64
  res << f.read
35
65
  end
66
+
36
67
  render :text => res
37
68
  }
38
69
  end
39
70
  end
40
71
 
72
+
41
73
  # Main dispatcher of the HTTP requests. The URL contains the name of the component,
42
74
  # as well as the method of this component to be called, according to the double underscore notation.
43
75
  # E.g.: some_grid__post_grid_data.
@@ -67,4 +99,4 @@ class NetzkeController < ApplicationController
67
99
  res.join("\n")
68
100
  end
69
101
 
70
- end
102
+ end
@@ -0,0 +1,2 @@
1
+ # This dummy file is only here to please cucumber environment
2
+ test:
@@ -19,7 +19,6 @@ Feature: Component loader
19
19
  When I press "Load with feedback"
20
20
  Then I should see "Callback invoked!"
21
21
 
22
-
23
22
  @selenium
24
23
  Scenario: Component loader should load a window component with another component in it
25
24
  Given I am on the ComponentLoader test page
@@ -27,4 +26,10 @@ Feature: Component loader
27
26
  Then I should see "Simple Component Inside Window"
28
27
  And I should see "Inner text"
29
28
 
29
+ @selenium
30
+ Scenario: Component loader should load a component with params properly
31
+ Given I am on the ComponentLoader test page
32
+ When I press "Load with params"
33
+ Then I should see "Simple Component with changed HTML"
34
+
30
35
 
@@ -23,9 +23,11 @@ Feature: Composition
23
23
  Scenario: Server should be able to address (deeply) nested components
24
24
  Given I am on the SomeComposite test page
25
25
  When I press "Update west from server"
26
+ And I sleep 1 second
26
27
  Then I should see "Here's an update for west panel"
27
28
 
28
29
  When I press "Update east south from server"
30
+ And I sleep 1 second
29
31
  Then I should see "Here's an update for south panel in east panel"
30
32
 
31
33
 
@@ -0,0 +1,18 @@
1
+ Feature: JsMixins
2
+ In order to value
3
+ As a role
4
+ I want feature
5
+
6
+ @javascript
7
+ Scenario: ComponentWithJsMixin should behave
8
+ Given I am on the ComponentWithJsMixin test page
9
+ When I press "Action one"
10
+ Then I should see "Action One triggered!"
11
+ When I press "Action two"
12
+ Then I should see "Action Two triggered!"
13
+
14
+ @javascript
15
+ Scenario: ExtendedComponentWithJsMixin should behave, too
16
+ Given I am on the ExtendedComponentWithJsMixin test page
17
+ When I press "Action three"
18
+ Then I should see "Action Three triggered!"
@@ -0,0 +1,9 @@
1
+ Feature: Nested views
2
+ In order to value
3
+ As a role
4
+ I want feature
5
+
6
+ @javascript
7
+ Scenario: A component with auto loaded content should render properly
8
+ When I go to the "panel with autoload" view
9
+ Then I should see "Autoloaded Panel"
@@ -6,11 +6,13 @@ Feature: Persistence
6
6
  @javascript
7
7
  Scenario: The component with persistence should be able to store and retrieve a persistence setting
8
8
  When I go to the ComponentWithSessionPersistence test page
9
- Then I should see "No Title (yet!)"
9
+ Then I should see "Default Title"
10
+ And I should see "Default HTML"
11
+ But I should not see "Title From Session"
12
+ And I should not see "HTML from session"
10
13
 
11
14
  When I press "Tell server to store new title"
12
15
  And I go to the ComponentWithSessionPersistence test page
13
- Then I should see "New Title!"
14
-
15
-
16
+ Then I should see "Title From Session"
17
+ And I should see "HTML from session"
16
18
 
@@ -26,6 +26,9 @@ module NavigationHelpers
26
26
  when /the component loader page/
27
27
  '/panel/component_loader'
28
28
 
29
+ when /the "(.*)" view/
30
+ embedded_components_path(:action => $1.gsub(" ", "_"))
31
+
29
32
  when /the (.*) test page/
30
33
  components_path(:component => $1)
31
34
 
data/javascripts/core.js CHANGED
@@ -11,7 +11,7 @@ Ext.BLANK_IMAGE_URL = Netzke.RelativeExtUrl + "/resources/images/default/s.gif";
11
11
  Ext.ns('Ext.netzke'); // namespace for extensions that depend on Ext
12
12
 
13
13
  Netzke.isLoading=function () {
14
- return Netzke.runningRequests!=0;
14
+ return Netzke.runningRequests!=0;
15
15
  }
16
16
  Netzke.runningRequests=0
17
17
 
@@ -23,25 +23,17 @@ Netzke.deprecationWarning = function(msg){
23
23
  }
24
24
  };
25
25
 
26
- // Check Ext JS version
27
- (function(){
28
- var requiredExtVersion = "3.3.0";
29
- var currentExtVersion = Ext.version;
30
- if (requiredExtVersion !== currentExtVersion) {
31
- Netzke.deprecationWarning("Netzke needs Ext " + requiredExtVersion + ". You have " + currentExtVersion + ".");
32
- }
33
- })();
34
-
35
26
  Ext.ns('Netzke.page'); // namespace for all component instantces on the page
36
27
  Ext.ns('Netzke.classes'); // namespace for all component classes
37
-
38
- // Because of Netzke's double-underscore notation, Ext.TabPanel should have a different id-delimiter (yes, this should be in netzke-core)
39
- Ext.TabPanel.prototype.idDelimiter = "___";
40
-
41
- Ext.QuickTips.init();
42
-
43
- // We don't want no state managment by default, thank you!
44
- Ext.state.Provider.prototype.set = function(){};
28
+ Ext.ns('Netzke.classes.Core'); // namespace for all component classes
29
+
30
+ Netzke.chainApply = function(){
31
+ var res = {};
32
+ Ext.each(arguments, function(o){
33
+ Ext.apply(res, o);
34
+ });
35
+ return res;
36
+ };
45
37
 
46
38
  // Some Ruby-ish String extensions
47
39
  // from http://code.google.com/p/inflection-js/
@@ -84,532 +76,179 @@ String.prototype.underscore = function() {
84
76
  .replace(/([a-z\d])([A-Z])/g, '$1_$2')
85
77
  .replace(/-/g, '_')
86
78
  .toLowerCase();
87
- }
79
+ };
88
80
 
89
81
  // Usefull when using mixins
90
82
  Netzke.aliasMethodChain = function(klass, method, feature) {
91
83
  klass[method + "Without" + feature.capitalize()] = klass[method];
92
84
  klass[method] = klass[method + "With" + feature.capitalize()];
93
- }
94
-
95
- // Properties/methods common to all component classes
96
- Netzke.componentMixin = function(receiver){
97
- return {
98
- height: 400,
99
- border: false,
100
- isNetzke: true, // to distinguish Netzke components from regular Ext components
101
- latestResult: {}, // latest result returned from the server via an API call
102
-
103
- /*
104
- Overriding the constructor to only apply an "alias method chain" to initComponent
105
- */
106
- constructor : function(config){
107
- Netzke.aliasMethodChain(this, "initComponent", "netzke");
108
- receiver.superclass.constructor.call(this, config);
109
- },
110
-
111
- /* initComponent common for all Netzke components */
112
- initComponentWithNetzke : function(){
113
- this.normalizeActions();
114
-
115
- this.detectActions(this);
116
-
117
- this.detectComponents(this.items);
85
+ };
118
86
 
119
- this.normalizeTools();
87
+ Netzke.cache = [];
120
88
 
121
- this.processEndpoints();
89
+ // Registering a Netzke component
90
+ Netzke.reg = function(xtype, klass) {
91
+ if (!Ext.ComponentMgr.types[xtype]) {
92
+ Ext.reg(xtype, klass);
93
+ Netzke.cache.push(xtype);
94
+ }
95
+ };
122
96
 
123
- // This is where the references to different callback functions will be stored
124
- this.callbackHash = {};
97
+ Netzke.classes.Core.Mixin = {};
125
98
 
126
- // This is where we store the information about components that are currently being loaded with this.loadComponent()
127
- this.componentsBeingLoaded = {};
99
+ // Properties/methods common to all Netzke component classes
100
+ Netzke.componentMixin = Ext.applyIf(Netzke.classes.Core.Mixin, {
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
+ Overriding the constructor to only apply an "alias method chain" to initComponent
105
+ */
106
+ // constructor: function(config){
107
+ // Netzke.aliasMethodChain(this, "initComponent", "netzke");
108
+ // receiver.superclass.constructor.call(this, config);
109
+ // },
128
110
 
129
- // Set title
130
- if (this.mode === "config"){
131
- if (!this.title) {
132
- this.title = '[' + this.id + ']';
133
- } else {
134
- this.title = this.title + ' [' + this.id + ']';
135
- }
136
- } else {
137
- if (!this.title) {
138
- this.title = this.id.humanize();
139
- }
140
- }
111
+ /*
112
+ Dynamically creates methods for api points, so that we could later call them like: this.myEndpointMethod()
113
+ */
114
+ processEndpoints: function(){
115
+ var endpoints = this.endpoints || [];
116
+ endpoints.push('deliver_component'); // all Netzke components get this endpoint
117
+ Ext.each(endpoints, function(intp){
118
+ this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
119
+ }, this);
120
+ },
141
121
 
142
- // From everywhere accessible FeedbackGhost
143
- this.feedbackGhost = new Netzke.FeedbackGhost();
144
-
145
- // Call the original initComponent
146
- this.initComponentWithoutNetzke();
147
- },
148
-
149
- /*
150
- Dynamically creates methods for api points, so that we could later call them like: this.myEndpointMethod()
151
- */
152
- processEndpoints : function(){
153
- var endpoints = this.endpoints || [];
154
- endpoints.push('deliver_component'); // all Netzke components get this endpoint
155
- Ext.each(endpoints, function(intp){
156
- this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); }
122
+ /*
123
+ Detects component placeholders in the passed object (typically, "items"),
124
+ and merges them with the corresponding config from this.components.
125
+ This way it becomes ready to be instantiated properly by Ext.
126
+ */
127
+ detectComponents: function(o){
128
+ if (Ext.isObject(o)) {
129
+ if (o.items) this.detectComponents(o.items);
130
+ } else if (Ext.isArray(o)) {
131
+ var a = o;
132
+ Ext.each(a, function(el, i){
133
+ if (el.component) {
134
+ a[i] = Ext.apply(this.components[el.component.camelize(true)], el);
135
+ delete a[i].component;
136
+ } else if (el.items) this.detectComponents(el.items);
157
137
  }, this);
158
- },
159
-
160
- normalizeTools: function() {
161
- if (this.tools) {
162
- var normTools = [];
163
- Ext.each(this.tools, function(tool){
164
- // Create an event for each action (so that higher-level components could interfere)
165
- this.addEvents(tool.id+'click');
166
-
167
- var handler = this.toolActionHandler.createDelegate(this, [tool]);
168
- normTools.push({id : tool, handler : handler, scope : this});
169
- }, this);
170
- this.tools = normTools;
171
- }
172
- },
173
-
174
- /*
175
- Replaces actions configs with Ext.Action instances, assigning default handler to them
176
- */
177
- normalizeActions : function(){
178
- var normActions = {};
179
- for (var name in this.actions) {
180
- // Create an event for each action (so that higher-level components could interfere)
181
- this.addEvents(name+'click');
182
-
183
- // Configure the action
184
- var actionConfig = this.actions[name];
185
- actionConfig.customHandler = actionConfig.handler;
186
- actionConfig.handler = this.actionHandler.createDelegate(this); // handler common for all actions
187
- actionConfig.name = name;
188
- normActions[name] = new Ext.Action(actionConfig);
189
- }
190
- delete(this.actions);
191
- this.actions = normActions;
192
- },
193
-
194
- /*
195
- Detects action configs in the passed object, and replaces them with instances of Ext.Action created by normalizeActions().
196
- 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.
197
- */
198
- detectActions: function(o){
199
- if (Ext.isObject(o)) {
200
- if ((typeof o.handler === 'string') && Ext.isFunction(this[o.handler.camelize(true)])) {
201
- // This button config has a handler specified as string - replace it with reference to a real function if it exists
202
- o.handler = this[o.handler.camelize(true)].createDelegate(this);
203
- }
204
- // TODO: this should be configurable!
205
- Ext.each(["bbar", "tbar", "fbar", "menu", "items", "contextMenu", "buttons"], function(key){
206
- if (o[key]) {
207
- 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
208
- delete(o[key]);
209
- o[key] = items;
210
- this.detectActions(o[key]);
211
- }
212
- }, this);
213
- } else if (Ext.isArray(o)) {
214
- var a = o;
215
- Ext.each(a, function(el, i){
216
- if (Ext.isObject(el)) {
217
- if (el.action) {
218
- if (!this.actions[el.action.camelize(true)]) throw "Netzke: action '"+el.action+"' not defined";
219
- a[i] = this.actions[el.action.camelize(true)];
220
- delete(el);
221
- } else {
222
- this.detectActions(el);
223
- }
224
- }
225
- }, this);
226
- }
227
- },
228
-
229
- /*
230
- Detects component placeholders in the passed object (typically, "items"),
231
- and merges them with the corresponding config from this.components.
232
- This way it becomes ready to be instantiated properly by Ext.
233
- */
234
- detectComponents: function(o){
235
- if (Ext.isObject(o)) {
236
- if (o.items) this.detectComponents(o.items);
237
- } else if (Ext.isArray(o)) {
238
- var a = o;
239
- Ext.each(a, function(el, i){
240
- if (el.component) {
241
- a[i] = Ext.apply(this.components[el.component.camelize(true)], el);
242
- delete a[i].component;
243
- } else if (el.items) this.detectComponents(el.items);
244
- }, this);
245
- }
246
- },
247
-
248
- /*
249
- Loads a component. Config options:
250
- 'name' (required) - the name of the child component to load
251
- 'container' - the id of a panel with the 'fit' layout where the loaded component will be instantiated
252
- 'callback' - function that gets called after the component is loaded. It receives the component's instance as parameter.
253
- 'scope' - scope for the callback.
254
- */
255
- loadComponent: function(params){
256
- if (params.id) {
257
- params.name = params.id;
258
- Netzke.deprecationWarning("Using 'id' in loadComponent is deprecated. Use 'name' instead.");
259
- }
260
-
261
- params.name = params.name.underscore();
262
-
263
- // 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.
264
- var serverParams = params.params || {};
265
- serverParams.name = params.name;
266
-
267
- // Build the list of already loaded ("cached") classes
268
- serverParams.cache = [];
269
-
270
- for (var klass in Netzke.classes) {
271
- serverParams.cache.push(klass);
272
- }
273
-
274
- serverParams.cache = serverParams.cache.join();
275
-
276
- var storedConfig = this.componentsBeingLoaded[params.name] = {};
277
-
278
- // Remember where the loaded component should be inserted into
279
- if (params.container) {
280
- storedConfig.container = params.container;
281
- }
282
-
283
- // remember the passed callback for the future (per loaded component, as there may be simultaneous ongoing calls)
284
- if (params.callback) {
285
- storedConfig.callback = params.callback;
286
- storedConfig.scope = params.scope;
287
- // this.callbackHash[params.name.underscore()] = params.callback;
288
- }
289
-
290
- // remove the old component if the container is specified
291
- if (params.container) Ext.getCmp(params.container).removeChild();
138
+ }
139
+ },
292
140
 
293
- // do the remote API call
294
- this.deliverComponent(serverParams);
295
- },
141
+ /*
142
+ Evaluates CSS
143
+ */
144
+ evalCss : function(code){
145
+ var head = Ext.fly(document.getElementsByTagName('head')[0]);
146
+ Ext.DomHelper.append(head, {
147
+ tag: 'style',
148
+ type: 'text/css',
149
+ html: code
150
+ });
151
+ },
296
152
 
297
- /*
298
- Called by the server after we ask him to load a component
299
- */
300
- componentDelivered : function(config){
301
- if (this.fireEvent('componentload'), config) {
302
- var storedConfig = this.componentsBeingLoaded[config.name] || {};
303
- delete this.componentsBeingLoaded[config.name];
153
+ /*
154
+ Evaluates JS
155
+ */
156
+ evalJs : function(code){
157
+ eval(code);
158
+ },
304
159
 
305
- var componentInstance;
160
+ /*
161
+ Gets id in the context of provided parent.
162
+ For example, the components "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
+ },
306
169
 
307
- if (storedConfig.container) {
308
- var container = Ext.getCmp(storedConfig.container);
309
- componentInstance = container.instantiateChild(config);
170
+ /*
171
+ Executes a bunch of methods. This method is called almost every time a communication to the server takes place.
172
+ Thus the server side of a component can provide any set of commands to its client side.
173
+ Args:
174
+ - instructions: array of methods, in the order of execution.
175
+ Each item is an object in one of the following 2 formats:
176
+ 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
177
+ 2) {component:component_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child component
178
+
179
+ Example:
180
+ - [
181
+ // the same as this.feedback("Your order is accepted")
182
+ {feedback: "You order is accepted"},
183
+
184
+ // the same as this.getChildComponent('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}])
185
+ {component:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] },
186
+
187
+ // ... etc:
188
+ {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}},
189
+ {setColums:[{},{}]},
190
+ {setMenus:[{},{}]},
191
+ ...
192
+ ]
193
+ */
194
+ bulkExecute : function(instructions){
195
+ if (Ext.isArray(instructions)) {
196
+ Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this);
197
+ } else {
198
+ for (var instr in instructions) {
199
+ if (Ext.isFunction(this[instr])) {
200
+ // Executing the method. If arguments are an array, expand that into arguments.
201
+ this[instr].apply(this, Ext.isArray(instructions[instr]) ? instructions[instr] : [instructions[instr]]);
310
202
  } else {
311
- componentInstance = this.instantiateChild(config);
312
- }
313
-
314
- if (storedConfig.callback) {
315
- storedConfig.callback.call(storedConfig.scope || this, componentInstance);
316
- }
317
- }
318
- },
319
-
320
- /*
321
- Instantiates and inserts a component into a container with layout 'fit'.
322
- Arg: an JS object with the following keys:
323
- - id: id of the receiving container
324
- - config: configuration of the component to be instantiated and inserted into the container
325
- */
326
- // renderComponentInContainer : function(params){
327
- // var cont = Ext.getCmp(params.container);
328
- // if (cont) {
329
- // cont.instantiateChild(params.config);
330
- // } else {
331
- // this.instantiateChild(params.config);
332
- // }
333
- // },
334
-
335
- /*
336
- Returns the parent component
337
- */
338
- getParent: function(){
339
- // simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
340
- var idSplit = this.id.split("__");
341
- idSplit.pop();
342
- var parentId = idSplit.join("__");
343
-
344
- return parentId === "" ? null : Ext.getCmp(parentId);
345
- },
346
-
347
- /*
348
- Reloads current component (calls the parent to reload it as its component)
349
- */
350
- reload : function(){
351
- var parent = this.getParent();
352
- if (parent) {
353
- parent.loadComponent({id:this.localId(parent), container:this.ownerCt.id});
354
- } else {
355
- window.location.reload();
356
- }
357
- },
358
-
359
- /*
360
- Gets id in the context of provided parent.
361
- For example, the components "properties", being a child of "books" has global id "books__properties",
362
- which *is* its widegt's real id. This methods, with the instance of "books" passed as parameter,
363
- returns "properties".
364
- */
365
- localId : function(parent){
366
- return this.id.replace(parent.id + "__", "");
367
- },
368
-
369
- /*
370
- Reconfigures the component
371
- */
372
- reconfigure: function(config){
373
- this.ownerCt.instantiateChild(config)
374
- },
375
-
376
- /*
377
- Evaluates CSS
378
- */
379
- evalCss : function(code){
380
- var linkTag = document.createElement('style');
381
- linkTag.type = 'text/css';
382
- linkTag.innerHTML = code;
383
- document.body.appendChild(linkTag);
384
- },
385
-
386
- /*
387
- Evaluates JS
388
- */
389
- evalJs : function(code){
390
- eval(code);
391
- },
392
-
393
- /*
394
- Executes a bunch of methods. This method is called almost every time a communication to the server takes place.
395
- Thus the server side of a component can provide any set of commands to its client side.
396
- Args:
397
- - instructions: array of methods, in the order of execution.
398
- Each item is an object in one of the following 2 formats:
399
- 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
400
- 2) {component:component_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child component
401
-
402
- Example:
403
- - [
404
- // the same as this.feedback("Your order is accepted")
405
- {feedback: "You order is accepted"},
406
-
407
- // the same as this.getChildComponent('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}])
408
- {component:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] },
409
-
410
- // ... etc:
411
- {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}},
412
- {setColums:[{},{}]},
413
- {setMenus:[{},{}]},
414
- ...
415
- ]
416
- */
417
- bulkExecute : function(instructions){
418
- if (Ext.isArray(instructions)) {
419
- Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this);
420
- } else {
421
- for (var instr in instructions) {
422
- if (Ext.isFunction(this[instr])) {
423
- this[instr].apply(this, [instructions[instr]]); // execute the method
203
+ var childComponent = this.getChildComponent(instr);
204
+ if (childComponent) {
205
+ childComponent.bulkExecute(instructions[instr]);
424
206
  } else {
425
- var childComponent = this.getChildComponent(instr);
426
- if (childComponent) {
427
- childComponent.bulkExecute(instructions[instr]);
428
- } else {
429
- throw "Netzke: Unknown method or child component '" + instr +"' in component '" + this.id + "'"
430
- }
207
+ throw "Netzke: Unknown method or child component '" + instr +"' in component '" + this.id + "'"
431
208
  }
432
209
  }
433
210
  }
434
- },
435
-
436
- // Get the child component
437
- getChildComponent : function(id){
438
- if (id === "") {return this};
439
- id = id.underscore();
440
- var split = id.split("__");
441
- if (split[0] === 'parent') {
442
- split.shift();
443
- var childInParentScope = split.join("__");
444
- return this.getParent().getChildComponent(childInParentScope);
445
- } else {
446
- return Ext.getCmp(this.id+"__"+id);
447
- }
448
- },
449
-
450
- // Common handler for all component's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
451
- // actionHandler : function(comp){
452
- // var actionName = comp.name;
453
- // // If firing corresponding event doesn't return false, call the handler
454
- // if (this.fireEvent(actionName+'click', comp)) {
455
- // var action = this.actions[actionName];
456
- // var customHandler = action.initialConfig.customHandler;
457
- // var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
458
- // if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
459
- //
460
- // // call the handler passing it the triggering component
461
- // this[methodName](comp);
462
- // }
463
- // },
464
- //
465
- // // Common handler for tools
466
- // toolActionHandler : function(tool){
467
- // // If firing corresponding event doesn't return false, call the handler
468
- // if (this.fireEvent(tool.id+'click')) {
469
- // var methodName = "on"+tool.camelize();
470
- // if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"}
471
- // this[methodName]();
472
- // }
473
- // },
474
-
475
- // Returns API url based on provided API point
476
- buildApiUrl: function(endpoint){
477
- Netzke.deprecationWarning("buildApiUrl() is deprecated. Use endpointUrl() first");
478
- return this.endpointUrl(endpoint);
479
- },
480
-
481
- endpointUrl: function(endpoint){
482
- return Netzke.RelativeUrlRoot + "/netzke/" + this.id + "__" + endpoint;
483
- },
484
-
485
- // Does the call to the server and processes the response
486
- callServer : function(intp, params, callback, scope){
487
- Netzke.runningRequests++;
488
- if (!params) params = {};
489
- Ext.Ajax.request({
490
- params: params,
491
- url: this.endpointUrl(intp),
492
- callback: function(options, success, response){
493
- if (success && response.responseText) {
494
- // execute commands from server
495
- this.bulkExecute(Ext.decode(response.responseText));
496
-
497
- // provide callback if needed
498
- if (typeof callback == 'function') {
499
- if (!scope) scope = this;
500
- callback.apply(scope, [this.latestResult]);
501
- }
502
- }
503
- },
504
- scope : this
505
- });
506
- Netzke.runningRequests--;
507
- },
508
-
509
- setResult: function(result) {
510
- this.latestResult = result;
511
- },
512
-
513
- // At this moment component is fully initializied
514
- commonAfterConstructor : function(config){
515
-
516
- // Add the menus
517
- if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);}
518
-
519
- // generic events
520
- this.addEvents(
521
- 'componentload' // fired when a child is dynamically loaded
522
- );
523
-
524
- // Cleaning up on destroy
525
- this.on('beforedestroy', function(){
526
- this.cleanUpMenu();
527
- }, this);
528
-
529
- this.callbackHash = {};
211
+ }
212
+ },
530
213
 
531
- if (this.afterConstructor) this.afterConstructor(config);
532
- },
214
+ // Returns API url based on provided API point
215
+ buildApiUrl: function(endpoint){
216
+ Netzke.deprecationWarning("buildApiUrl() is deprecated. Use endpointUrl() instead.");
217
+ return this.endpointUrl(endpoint);
218
+ },
533
219
 
534
- feedback:function(msg){
535
- if (this.initialConfig && this.initialConfig.quiet) {
536
- return false;
537
- }
220
+ endpointUrl: function(endpoint){
221
+ return Netzke.RelativeUrlRoot + "/netzke/" + this.id + "__" + endpoint;
222
+ },
538
223
 
539
- if (this.feedbackGhost) {
540
- this.feedbackGhost.showFeedback(msg);
541
- } else {
542
- // there's no application to show the feedback - so, we do it ourselves
543
- if (typeof msg == 'string'){
544
- alert(msg);
545
- } else {
546
- var compoundResponse = "";
547
- Ext.each(msg, function(m){
548
- compoundResponse += m.msg + "\n"
549
- });
550
- if (compoundResponse != "") {
551
- alert(compoundResponse);
224
+ // Does the call to the server and processes the response
225
+ callServer : function(intp, params, callback, scope){
226
+ Netzke.runningRequests++;
227
+ if (!params) params = {};
228
+ Ext.Ajax.request({
229
+ params: params,
230
+ url: this.endpointUrl(intp),
231
+ callback: function(options, success, response){
232
+ if (success && response.responseText) {
233
+ // execute commands from server
234
+ this.bulkExecute(Ext.decode(response.responseText));
235
+
236
+ // provide callback if needed
237
+ if (typeof callback == 'function') {
238
+ if (!scope) scope = this;
239
+ callback.apply(scope, [this.latestResult]);
552
240
  }
553
241
  }
554
- }
555
- },
556
-
557
- // addMenu : function(menu, owner){
558
- // if (!owner) {
559
- // owner = this;
560
- // }
561
- //
562
- // if (!!this.hostMenu) {
563
- // this.hostMenu(menu, owner);
564
- // } else {
565
- // if (this.ownerComponent) {
566
- // this.ownerComponent.addMenu(menu, owner);
567
- // }
568
- // }
569
- // },
570
- //
571
- // cleanUpMenu : function(owner){
572
- // if (!owner) {
573
- // owner = this;
574
- // }
575
- //
576
- // if (!!this.unhostMenu) {
577
- // this.unhostMenu(owner);
578
- // } else {
579
- // if (this.ownerComponent) {
580
- // this.ownerComponent.cleanUpMenu(owner);
581
- // }
582
- // }
583
- // },
584
-
585
- // Common handler for all component's actions. <tt>comp</tt> is the Component that triggered the action (e.g. button or menu item)
586
- actionHandler : function(comp){
587
- var actionName = comp.name;
588
- // If firing corresponding event doesn't return false, call the handler
589
- if (this.fireEvent(actionName+'click', comp)) {
590
- var action = this.actions[actionName];
591
- var customHandler = action.initialConfig.customHandler;
592
- var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize();
593
- if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"}
594
-
595
- // call the handler passing it the triggering component
596
- this[methodName](comp);
597
- }
598
- },
599
-
600
- // Common handler for tools
601
- toolActionHandler : function(tool){
602
- // If firing corresponding event doesn't return false, call the handler
603
- if (this.fireEvent(tool.id+'click')) {
604
- var methodName = "on"+tool.camelize();
605
- if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"}
606
- this[methodName]();
607
- }
608
- },
242
+ },
243
+ scope : this
244
+ });
245
+ Netzke.runningRequests--;
246
+ },
609
247
 
610
- onComponentLoad:Ext.emptyFn // gets overridden
248
+ setResult: function(result) {
249
+ this.latestResult = result;
611
250
  }
612
- }
251
+ });
613
252
 
614
253
 
615
254
  // Netzke extensions for Ext.Container
@@ -623,7 +262,14 @@ Ext.override(Ext.Container, {
623
262
  } else {
624
263
  this.remove(this.getNetzkeComponent()); // first delete previous component
625
264
  this.add(instance);
626
- this.doLayout();
265
+
266
+ // Sometimes a child is getting loaded into a hidden container...
267
+ if (this.isVisible()) {
268
+ this.doLayout();
269
+ } else {
270
+ this.on('show', function(cmp){cmp.doLayout();}, {single: true});
271
+ }
272
+
627
273
  }
628
274
  return instance;
629
275
  },
@@ -654,7 +300,6 @@ Ext.override(Ext.Container, {
654
300
  removeChild : function(){
655
301
  this.remove(this.getNetzkeComponent());
656
302
  }
657
-
658
303
  });
659
304
 
660
305
 
@@ -672,7 +317,9 @@ Ext.apply(Netzke.FeedbackGhost.prototype, {
672
317
 
673
318
  var showBox = function(msg, lvl){
674
319
  if (!lvl) {lvl = 'notice'};
675
- var msgCt = Ext.DomHelper.insertFirst(document.body, {'class':'netzke-feedback'}, true);
320
+
321
+ var msgCt = Ext.get('netzke-feedback') || Ext.DomHelper.insertFirst(document.body, {id: 'netzke-feedback', 'class':'netzke-feedback'}, true);
322
+
676
323
  var m = Ext.DomHelper.append(msgCt, {html:createBox(msg,lvl)}, true);
677
324
  m.slideIn('t').pause(2).ghost("b", {remove:true});
678
325
  }