netzke-core 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }