netzke-core 0.12.3 → 1.0.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG.md +65 -607
  2. data/Gemfile +1 -1
  3. data/LICENSE +2 -6
  4. data/README.md +77 -145
  5. data/javascripts/base.js +582 -96
  6. data/javascripts/core.js +62 -0
  7. data/javascripts/notifications.js +43 -0
  8. data/javascripts/remoting_provider.js +29 -0
  9. data/javascripts/routing.js +63 -0
  10. data/lib/netzke/base.rb +16 -83
  11. data/lib/netzke/core/action_config.rb +1 -5
  12. data/lib/netzke/core/actions.rb +59 -21
  13. data/lib/netzke/core/{client_class.rb → client_class_config.rb} +81 -73
  14. data/lib/netzke/core/client_code.rb +157 -0
  15. data/lib/netzke/core/component_config.rb +2 -2
  16. data/lib/netzke/core/composition.rb +85 -65
  17. data/lib/netzke/core/configuration.rb +26 -14
  18. data/lib/netzke/core/core_i18n.rb +17 -0
  19. data/lib/netzke/core/css_config.rb +6 -6
  20. data/lib/netzke/core/dynamic_assets.rb +17 -24
  21. data/lib/netzke/core/embedding.rb +2 -2
  22. data/lib/netzke/core/endpoint_response.rb +8 -2
  23. data/lib/netzke/core/inheritance.rb +33 -0
  24. data/lib/netzke/core/plugins.rb +1 -4
  25. data/lib/netzke/core/railz/action_view_ext.rb +1 -1
  26. data/lib/netzke/core/railz/controller_extensions.rb +21 -15
  27. data/lib/netzke/core/services.rb +61 -48
  28. data/lib/netzke/core/session.rb +0 -2
  29. data/lib/netzke/core/state.rb +11 -9
  30. data/lib/netzke/core/stylesheets.rb +3 -3
  31. data/lib/netzke/core/version.rb +1 -1
  32. data/lib/netzke/core.rb +3 -3
  33. data/lib/netzke/plugin.rb +2 -6
  34. data/stylesheets/core.css +2 -2
  35. metadata +11 -10
  36. data/TODO.md +0 -9
  37. data/javascripts/ext.js +0 -518
  38. data/lib/netzke/core/config_to_dsl_delegator.rb +0 -62
  39. data/lib/netzke/core/dsl_support.rb +0 -70
  40. data/lib/netzke/core/html.rb +0 -29
  41. data/lib/netzke/core/javascript.rb +0 -123
@@ -1,31 +1,32 @@
1
1
  module Netzke
2
2
  module Core
3
- # This class is responsible of creation of the client (JavaScript) class. It is passed as block parameter to the `js_configure` DSL method:
3
+ # This class is responsible of creation of the client (JavaScript) class. It is passed as block parameter to the `client_class` DSL method:
4
4
  #
5
5
  # class MyComponent < Netzke::Base
6
- # js_configure do |c|
6
+ # client_class do |c|
7
7
  # c.extend = "Ext.form.Panel"
8
8
  # end
9
9
  # end
10
- class ClientClass
11
- attr_accessor :required_files, :base_class, :properties, :netzke_mixins, :translated_properties
10
+ class ClientClassConfig
11
+ attr_accessor :base_class, :properties, :translated_properties, :dir, :requires_as_string
12
12
 
13
- def initialize(klass)
13
+ def initialize(klass, called_from)
14
14
  @klass = klass
15
- @required_files = []
16
- @netzke_mixins = []
15
+ @called_from = @dir = called_from
16
+ @requires_as_string = ""
17
+ @explicit_override_paths = []
17
18
  @properties = {
18
19
  extend: extended_class,
19
20
  alias: class_alias,
20
21
  }
21
- @properties[:mixins] = ['Netzke.classes.Core.Mixin'] if extending_extjs_component?
22
+ @properties[:mixins] = ['Netzke.Base'] if extending_extjs_component?
22
23
  @translated_properties = []
23
24
  end
24
25
 
25
26
  # Allows assigning JavaScript prototype properties, including functions:
26
27
  #
27
28
  # class MyComponent < Netzke::Base
28
- # js_configure do |c|
29
+ # client_class do |c|
29
30
  # # this will result in the +title+ property defined on the client class prototype
30
31
  # c.title = "My cool component"
31
32
  #
@@ -38,19 +39,19 @@ module Netzke
38
39
  # end
39
40
  # end
40
41
  #
41
- # An alternative way to define prototype properties is by using "mixins", see {ClientClass#mixin}
42
+ # An alternative way to define prototype properties is by using {ClientClassConfig#include}
42
43
  #
43
- # Class attributes are accessible from inside +js_configure+:
44
+ # As attributes are accessible from inside +client_class+:
44
45
  #
45
46
  # class MyComponent < Netzke::Base
46
47
  # class_attribute :title
47
48
  # self.title = "Some default title"
48
- # js_configure do |c|
49
+ # client_class do |c|
49
50
  # c.title = self.title
50
51
  # end
51
52
  # end
52
53
  #
53
- # Now you can configure your component on a class level like this:
54
+ # ... you can configure your component on a class level like this:
54
55
  #
55
56
  # # e.g. in Rails initializers
56
57
  # MyComponent.title = "New title for all MyComponents"
@@ -76,36 +77,37 @@ module Netzke
76
77
  # Symbols will be expanded following a convention, e.g.:
77
78
  #
78
79
  # class MyComponent < Netzke::Base
79
- # js_configure do |c|
80
+ # client_class do |c|
80
81
  # c.require :some_library
81
82
  # end
82
83
  # end
83
84
  #
84
- # This will "require" a JavaScript file +{component_location}/my_component/javascripts/some_library.js+
85
+ # This will "require" a JavaScript file +{component_location}/my_component/client/some_library.js+
85
86
  #
86
87
  # Strings will be interpreted as full paths to the required JavaScript file:
87
88
  #
88
- # js_configure do |c|
89
+ # client_class do |c|
89
90
  # c.require "#{File.dirname(__FILE__)}/my_component/one.js", "#{File.dirname(__FILE__)}/my_component/two.js"
90
91
  # end
91
- def require(*args)
92
- callr = caller.first
93
-
94
- @required_files |= args.map{ |a| a.is_a?(Symbol) ? expand_require_path(a, callr) : a }
92
+ def require(*refs)
93
+ raise(ArgumentError, "wrong number of arguments (0 for 1 or more)") if refs.empty?
94
+ refs.each do |ref|
95
+ @requires_as_string << require_from_file(normalize_filepath(ref))
96
+ end
95
97
  end
96
98
 
97
- # Use it to "mixin" JavaScript objects defined in a separate file. It may accept one or more symbols or strings.
99
+ # Use it to "include" JavaScript methods defined in a separate file. Behind the scenes it uses `Ext.Class.override` It may accept one or more symbols or strings.
98
100
  #
99
101
  # Symbols will be expanded following a convention, e.g.:
100
102
  #
101
103
  # class MyComponent < Netzke::Base
102
- # js_configure do |c|
103
- # c.mixin :some_functionality
104
+ # client_class do |c|
105
+ # c.include :some_functionality
104
106
  # #...
105
107
  # end
106
108
  # end
107
109
  #
108
- # This will "mixin" a JavaScript object defined in a file named +{component_location}/my_component/javascripts/some_functionality.js+, which way contain something like this:
110
+ # This will "include" a JavaScript object defined in the file named +{component_location}/my_component/client/some_functionality.js+, which way contain something like this:
109
111
  #
110
112
  # {
111
113
  # someProperty: 100,
@@ -114,15 +116,14 @@ module Netzke
114
116
  # }
115
117
  # }
116
118
  #
117
- # Also accepts a string, which will be interpreted as a full path to the file (useful for sharing mixins between classes).
118
- # With no parameters, will assume :<component_class_name>.
119
+ # Strings will be interpreted as a full path to the "included" file (useful for sharing client code between components).
119
120
  #
120
- # Also, see defining JavaScript prototype properties with {ClientClass#method_missing}.
121
- def mixin(*args)
122
- args << @klass.name.split("::").last.underscore.to_sym if args.empty?
123
- callr = caller.first
124
- args.each do |a|
125
- @netzke_mixins << (a.is_a?(Symbol) ? expand_require_path(a, callr) : a)
121
+ # Also, see defining JavaScript prototype properties with {ClientClassConfig#method_missing}.
122
+ def include(*refs)
123
+ raise(ArgumentError, "wrong number of arguments (0 for 1 or more)") if refs.empty?
124
+
125
+ refs.each do |ref|
126
+ @explicit_override_paths << normalize_filepath(ref)
126
127
  end
127
128
  end
128
129
 
@@ -137,7 +138,7 @@ module Netzke
137
138
  # E.g.:
138
139
  #
139
140
  # class MyComponent < Netzke::Base
140
- # js_configure do |c|
141
+ # client_class do |c|
141
142
  # c.translate :overwrite_confirm, :overwrite_confirm_title, :delete_confirm, :delete_confirm_title
142
143
  # end
143
144
  # end
@@ -153,11 +154,11 @@ module Netzke
153
154
  # Builds this component's xtype
154
155
  # E.g.: netzkebasepackwindow, netzkebasepackgridpanel
155
156
  def xtype
156
- "netzke" + @klass.name.gsub("::", "").downcase
157
+ @klass.name.gsub("::", "").downcase
157
158
  end
158
159
 
159
160
  # Component's JavaScript class declaration.
160
- # It gets stored in the JS class cache storage (Netzke.classes) at the client side to be reused at the moment of component instantiation.
161
+ # It gets stored in the JS class cache storage (Netzke.cache) at the client side to be reused at the moment of component instantiation.
161
162
  def class_code
162
163
  res = []
163
164
  # Defining the scope if it isn't known yet
@@ -173,21 +174,21 @@ Netzke.cache.push('#{xtype}');
173
174
  res.join("\n")
174
175
  end
175
176
 
176
- # Top level scope which will be used to scope out Netzke classes
177
+ # Additional scope for your Netzke components.
177
178
  def default_scope
178
- "Netzke.classes"
179
+ ""
179
180
  end
180
181
 
181
182
  # Returns the scope of this component
182
- # e.g. "Netzke.classes.Netzke.Basepack"
183
+ # e.g. "Netzke.Basepack"
183
184
  def scope
184
- [default_scope, *@klass.name.split("::")[0..-2]].join(".")
185
+ [default_scope.presence, *@klass.name.split("::")[0..-2]].compact.join(".")
185
186
  end
186
187
 
187
- # Returns the full name of the JavaScript class, including the scopes *and* the common scope, which is 'Netzke.classes'.
188
- # E.g.: "Netzke.classes.Netzke.Basepack.GridPanel"
188
+ # Returns the full name of the JavaScript class, including the scopes *and* the common scope, which is 'Netzke'.
189
+ # E.g.: "Netzke.Basepack.GridPanel"
189
190
  def class_name
190
- [scope, @klass.name.split("::").last].join(".")
191
+ [scope.presence, @klass.name.split("::").last].compact.join(".")
191
192
  end
192
193
 
193
194
  # Whether we have to inherit from an Ext JS component, or a Netzke component
@@ -195,29 +196,16 @@ Netzke.cache.push('#{xtype}');
195
196
  @klass.superclass == Netzke::Base
196
197
  end
197
198
 
198
- # Returns all required JavaScript files as a string
199
- def required
200
- res = ""
201
-
202
- # Prevent re-including code that was already required by the parent
203
- # (thus, only require those JS files when require_js was defined in the current class, not in its ancestors)
204
- # FIXME!
205
- ((@klass.singleton_methods(false).map(&:to_sym).include?(:include_js) ? include_js : []) + required_files).each do |path|
206
- f = File.new(path)
207
- res << f.read << "\n"
208
- end
209
-
210
- res
211
- end
212
-
213
199
  # JavaScript code needed for this particulaer class. Includes external JS code and the JS class definition for self.
214
200
  def code_with_dependencies
215
- [required, class_code].join("\n")
201
+ [requires_as_string, class_code].join("\n")
216
202
  end
217
203
 
218
204
  # Generates declaration of the JS class as direct extension of a Ext component
219
205
  def class_declaration
220
- %(Ext.define('#{class_name}', #{properties_as_string});)
206
+ %(Ext.define('#{class_name}', #{properties_as_string});
207
+
208
+ #{overrides_as_string})
221
209
  end
222
210
 
223
211
  # Alias prefix: 'widget' for components, 'plugin' for plugins
@@ -225,27 +213,47 @@ Netzke.cache.push('#{xtype}');
225
213
  @klass < Netzke::Plugin ? "plugin" : "widget"
226
214
  end
227
215
 
228
- def mixins_as_string
229
- netzke_mixins.presence && netzke_mixins.map do |f|
230
- as_string = File.read(f)
231
- as_string.sub!('{', ' ')
232
- as_string[as_string.rindex('}')] = ' '
233
- as_string.rstrip
234
- end.join(",\n")
235
- end
236
-
237
216
  def properties_as_string
238
- [properties.netzke_jsonify.to_json.chop, mixins_as_string].compact.join(",\n") + "}"
217
+ [properties.netzke_jsonify.to_json.chop].compact.join(",\n") + "}"
239
218
  end
240
219
 
241
220
  # Default extended class
242
221
  def extended_class
243
- extending_extjs_component? ? "Ext.panel.Panel" : @klass.superclass.js_config.class_name
222
+ extending_extjs_component? ? "Ext.panel.Panel" : @klass.superclass.client_class_config.class_name
223
+ end
224
+
225
+ def expand_client_code_path(ref)
226
+ "#{@dir}/client/#{ref}.js"
227
+ end
228
+
229
+ def override_paths
230
+ return @override_paths if @override_paths
231
+
232
+ @override_paths = @explicit_override_paths
233
+ @dir = @called_from
234
+ default_override_path = expand_client_code_path(@dir.split("/").last)
235
+ @override_paths.prepend(default_override_path) if File.exist?(default_override_path)
236
+ @override_paths
237
+ end
238
+
239
+ def overrides_as_string
240
+ override_paths.map { |path| override_from_file(path) }.join("\n\n")
241
+ end
242
+
243
+ private
244
+
245
+ def override_from_file(path)
246
+ str = File.read(path)
247
+ str.chomp!("\n")
248
+ %(#{class_name}.override(#{str});)
249
+ end
250
+
251
+ def require_from_file(path)
252
+ File.new(path).read + "\n"
244
253
  end
245
254
 
246
- private
247
- def expand_require_path(sym, callr)
248
- %Q(#{callr.split(".rb:").first}/javascripts/#{sym}.js)
255
+ def normalize_filepath(ref)
256
+ ref.is_a?(Symbol) ? expand_client_code_path(ref) : ref
249
257
  end
250
258
  end
251
259
  end
@@ -0,0 +1,157 @@
1
+ module Netzke::Core
2
+ # Client class definition and instantiation.
3
+ module ClientCode
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Configures client class
8
+ # Example:
9
+ #
10
+ # client_class do |c|
11
+ # # c is an instance of ClientClassConfig
12
+ # c.title = "My title"
13
+ # c.require :extra_js
14
+ # # ...etc
15
+ # end
16
+ #
17
+ # For more details see {Netzke::Core::ClientClassConfig}
18
+ def client_class &block
19
+ raise ArgumentError, "client_class called without block" unless block_given?
20
+ @configure_blocks ||= []
21
+ @configure_blocks << [block, dir(caller.first)]
22
+ end
23
+
24
+ # Class-level client class config.
25
+ # Note: late evaluation of `client_class` blocks allows us using class-level configs in those blocks, e.g.:
26
+ #
27
+ # class ConfigurableOnClassLevel < Netzke::Base
28
+ # class_attribute :title
29
+ # self.title = "Default"
30
+ # client_class do |c|
31
+ # c.title = self.title
32
+ # end
33
+ # end
34
+ #
35
+ # ConfigurableOnClassLevel.title = "Overridden"
36
+ def client_class_config
37
+ return @client_class_config if @client_class_config
38
+
39
+ @client_class_config = Netzke::Core::ClientClassConfig.new(self, called_from)
40
+
41
+ (@configure_blocks || []).each do |block, dir|
42
+ @client_class_config.dir = dir
43
+ block.call(@client_class_config) if block
44
+ end
45
+
46
+ @client_class_config
47
+ end
48
+
49
+ # Path to the dir with this component/extension's extra code (ruby modules, scripts, stylesheets)
50
+ def dir(cllr)
51
+ %Q(#{cllr.split(".rb:").first})
52
+ end
53
+ end
54
+
55
+ # Builds {#js_config} used for instantiating a client class. Override it when you need to extend/modify the config for the JS component intance. It's *not* being called when the *server* class is being instantiated (e.g. to process an endpoint call). With other words, it's only being called before a component is first being loaded in the browser. so, it's ok to do heavy stuf fhere, like building a tree panel nodes from the database.
56
+ def configure_client(c)
57
+ c.merge!(normalized_config)
58
+
59
+ %w[id item_id path netzke_components endpoints xtype alias i18n netzke_plugins].each do |thing|
60
+ js_thing = send(:"js_#{thing}")
61
+ c[thing] = js_thing if js_thing.present?
62
+ c.client_config = client_config.netzke_literalize_keys # because this is what we'll get back from client side as server config, and the keys must be snake_case
63
+ end
64
+
65
+ # reset component session
66
+ # TODO: also remove empty hashes from the global session
67
+ component_session.clear
68
+ end
69
+
70
+ def js_path
71
+ @path
72
+ end
73
+
74
+ # Global id in the component tree, following the double-underscore notation, e.g. +books__config_panel__form+
75
+ def js_id
76
+ @js_id ||= parent.nil? ? @item_id : [parent.js_id, @item_id].join("__")
77
+ end
78
+
79
+ def js_xtype
80
+ self.class.client_class_config.xtype
81
+ end
82
+
83
+ def js_item_id
84
+ @item_id
85
+ end
86
+
87
+ # Ext.createByAlias may be used to instantiate the component.
88
+ def js_alias
89
+ self.class.client_class_config.class_alias
90
+ end
91
+
92
+ def js_endpoints
93
+ self.class.endpoints.keys.map{ |p| p.to_s.camelcase(:lower) }
94
+ end
95
+
96
+ def js_netzke_plugins
97
+ plugins.map{ |p| p.to_s.camelcase(:lower) }
98
+ end
99
+
100
+ # Instance-level client class config. The result of this method (a hash) is converted to a JSON object and passed as options to the constructor of our JavaScript class.
101
+ # Not to be overridden, override {#configure_client} instead.
102
+ def js_config
103
+ @js_config ||= ActiveSupport::OrderedOptions.new.tap{|c| configure_client(c)}
104
+ end
105
+
106
+ # Hash containing configuration for all child components to be instantiated at the JS side
107
+ def js_components
108
+ @js_components ||= eagerly_loaded_components.inject({}) do |out, name|
109
+ instance = component_instance(name.to_sym)
110
+ out.merge(name => instance.js_config)
111
+ end
112
+ end
113
+
114
+ alias js_netzke_components js_components
115
+
116
+ # All the JS-code required by this instance of the component to be instantiated in the browser, excluding cached
117
+ # code.
118
+ # It includes JS-classes for the parents, eagerly loaded child components, and itself.
119
+ def js_missing_code(cached = [])
120
+ code = dependency_classes.inject("") do |r,k|
121
+ cached.include?(k.client_class_config.xtype) ? r : r + k.client_class_config.code_with_dependencies
122
+ end
123
+ code.blank? ? nil : Netzke::Core::DynamicAssets.minify_js(code)
124
+ end
125
+
126
+ protected
127
+
128
+ # Allows referring to client-side function that will be called in the scope of the component. Handy to specify
129
+ # handlers for tools/actions, or any other functions that have to be passed as configuration to different Ext JS
130
+ # components. Usage:
131
+ #
132
+ # class MyComponent < Netzke::Base
133
+ # def configure(c)
134
+ # super
135
+ # c.bbar = [{text: 'Export', handler: f(:do_export)}]
136
+ # end
137
+ # end
138
+ #
139
+ # As a result, `MyComponent`'s client-side `doExport` function will be called in the component's scope, receiving all the
140
+ # usual handler parameters from Ext JS.
141
+ # Read more on how to define client-side functions in `Netzke::Core::ClientClassConfig`.
142
+ def f(name)
143
+ Netzke::Core::JsonLiteral.new("function(){var c=Ext.getCmp('#{js_id}'); return c.#{name.to_s.camelize(:lower)}.apply(c, arguments);}")
144
+ end
145
+
146
+ private
147
+
148
+ # Merges all the translations in the class hierarchy
149
+ # Note: this method can't be moved out to ClientClassConfig, because I18n is loaded only once, when other Ruby classes are evaluated; so, this must remain at instance level.
150
+ def js_i18n
151
+ @js_i18n ||= self.class.netzke_ancestors.inject({}) do |r,klass|
152
+ hsh = klass.client_class_config.translated_properties.inject({}) { |h,t| h.merge(t => I18n.t("#{klass.i18n_id}.#{t}")) }
153
+ r.merge(hsh)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -6,8 +6,8 @@ module Netzke::Core
6
6
  end
7
7
 
8
8
  def set_defaults!
9
- self.item_id ||= name # default item_id
10
- self.klass ||= self.class_name.try(:constantize) || name.camelize.constantize # default klass
9
+ self.item_id ||= name
10
+ self.klass ||= self.class_name.try(:constantize) || name.camelize.constantize
11
11
  end
12
12
  end
13
13
  end
@@ -46,7 +46,7 @@ module Netzke::Core
46
46
  # ]
47
47
  # end
48
48
  #
49
- # If an extra (layout) configuration should be provided, a component can be referred to by using the +component+ key in the configuration hash (this can be useful when overriding a layout of a child component):
49
+ # If extra (layout) configuration is needed, a component can be referred to by using the +component+ key in the configuration hash (this can be useful when overriding a layout of a child component):
50
50
  #
51
51
  # component :tab_one # ...
52
52
  # component :tab_two # ...
@@ -61,11 +61,10 @@ module Netzke::Core
61
61
  #
62
62
  # == Lazily vs eagerly loaded components
63
63
  #
64
- # By default, if a component is not used in the layout, it is lazily loaded, which means that the code for this component is not loaded in the browser until the moment the component gets dynamically loaded by the JavaScript method `netzkeLoadComponent` (see {Netzke::Core::Javascript}). Referring a component in the layout (the `items` property) automatically makes it eagerly loaded. Sometimes it's desired to eagerly load a component without using it directly in the layout (an example can be a window that we need to render instantly without requesting the server). In this case an option `eager_loading` can be set to true:
64
+ # By default, if a component is not used in the layout, it is lazily loaded, which means that the code for this component is not loaded in the browser until the moment the component gets dynamically loaded by the JavaScript method `netzkeLoadComponent` (see {Netzke::Core::ClientCode}). Referring a component in the layout (the `items` property) automatically makes it eagerly loaded. Sometimes it's desired to eagerly load a component without using it directly in the layout (an example can be a window that we need to render instantly without requesting the server). In this case an option `eager_load` can be set to true:
65
65
  #
66
- # component :eagerly_loaded_window do |c|
66
+ # component :eagerly_loaded_window, eager_load: true do |c|
67
67
  # c.klass = SomeWindowComponent
68
- # c.eager_loading = true
69
68
  # end
70
69
  #
71
70
  # == Dynamic component loading
@@ -76,78 +75,107 @@ module Netzke::Core
76
75
  #
77
76
  # You can make a child component inavailable for dynamic loading by using the +excluded+ option. When an excluded component is used in the layout, it will be skipped.
78
77
  # This can be used for authorization.
78
+ #
79
+ # == Preventing name clashing with actions
80
+ #
81
+ # If a component has an action and a child component sharing the name, referring to them by symbols in the
82
+ # configuration will result in a name clash. In that case refer to the child component by a hash, e.g.:
83
+ #
84
+ # class Dashboard < Netzke::Base
85
+ # action :users
86
+ #
87
+ # component :users
88
+ #
89
+ # def configure(c)
90
+ # super
91
+ # c.bbar = [:users]
92
+ # c.items = [{component: :users}]
93
+ # end
94
+ # end
79
95
  module Composition
80
96
  extend ActiveSupport::Concern
81
97
 
98
+ module ClassMethods
99
+ def component(name, options = {}, &block)
100
+ define_method :"#{name}_component", &(block || ->(c){c})
101
+ # NOTE: "<<" won't work here as this will mutate the array shared between classes
102
+ self.eagerly_loaded_dsl_components += [name] if options[:eager_load]
103
+ end
104
+ end
105
+
82
106
  included do
83
- # Declares Base.component, for declaring child componets, and Base#components, which returns a [Hash] of all component configs by name
84
- declare_dsl_for :components, config_class: Netzke::Core::ComponentConfig
107
+ # Hash of components declared inline in the config
108
+ attr_accessor :inline_components
85
109
 
86
- attr_accessor :components_in_config
110
+ # Components declared in DSL and marked with `eager_load: true`
111
+ class_attribute :eagerly_loaded_dsl_components
112
+ self.eagerly_loaded_dsl_components = []
87
113
 
88
114
  # Loads a component on browser's request. Every Netzke component gets this endpoint.
89
115
  # +params+ should contain:
90
116
  # [cache] an array of component classes cached at the browser
91
117
  # [name] name of the child component to be loaded
92
118
  # [index] clone index of the loaded component
93
- endpoint :deliver_component do |params, this|
119
+ endpoint :deliver_component do |params|
94
120
  cache = params[:cache].split(",") # array of cached xtypes
95
121
  component_name = params[:name].underscore.to_sym
96
122
 
97
- item_id = "#{component_name}#{params[:index]}"
98
-
99
- cmp_instance = components[component_name] &&
100
- !components[component_name][:excluded] &&
101
- component_instance(component_name, {item_id: item_id, client_config: params[:client_config]})
123
+ item_id = params[:item_id]
124
+ cmp_instance = component_instance(component_name, {item_id: item_id, client_config: params[:client_config]})
102
125
 
103
126
  if cmp_instance
104
127
  js, css = cmp_instance.js_missing_code(cache), cmp_instance.css_missing_code(cache)
105
- this.netzke_eval_js(js) if js.present?
106
- this.netzke_eval_css(css) if css.present?
107
-
108
- this.netzke_component_delivered(cmp_instance.js_config);
128
+ { js: js, css: css, config: cmp_instance.js_config }
109
129
  else
110
- this.netzke_component_delivery_failed(item_id: item_id, msg: "Couldn't load component '#{item_id}'")
130
+ { error: "Couldn't load component '#{component_name}'" }
111
131
  end
112
132
  end
113
-
114
133
  end # included
115
134
 
116
- # @return [Hash] configs of eagerly loaded components by name
135
+ # @return [Array] names of eagerly loaded components
117
136
  def eagerly_loaded_components
118
- @eagerly_loaded_components ||= components.select{|k,v| components_in_config.include?(k) || v[:eager_loading]}
137
+ self.class.eagerly_loaded_dsl_components + @components_in_config
119
138
  end
120
139
 
121
140
  # Instantiates a child component by its name.
122
141
  # +params+ can contain:
123
142
  # [client_config] a config hash passed from the client class
124
- # [item_id] overridden item_id (used in case of multi-instance loading)
125
- def component_instance(name, options = {})
126
- if respond_to?(:"#{name}_component")
127
- cfg = ComponentConfig.new(name, self)
128
- cfg.client_config = options[:client_config] || {}
129
- cfg.item_id = options[:item_id]
130
- cfg.js_id = options[:js_id]
131
- send("#{name}_component", cfg)
132
- cfg.set_defaults!
133
- else
134
- cfg = ComponentConfig.new(name, self)
135
- cfg.merge!(components[name.to_sym])
136
- end
137
-
138
- component_instance_from_config(cfg) if cfg
143
+ # [item_id] overridden item_id, used in case of loading multiple instances of the same child component
144
+ def component_instance(name_or_config, overrides = {})
145
+ cfg = name_or_config.is_a?(Hash) ? name_or_config : component_config(name_or_config, overrides)
146
+ return nil if cfg.nil? || cfg[:excluded]
147
+ klass = cfg.klass || cfg.class_name.constantize
148
+ klass.new(cfg, self)
139
149
  end
140
150
 
141
- def component_instance_from_config(c)
142
- klass = c.klass || c.class_name.constantize
143
- klass.new(c, self)
151
+ # @return [Hash] Given component's name and overrides, returns complete component's config, ready for
152
+ # instantiation
153
+ def component_config(component_name, overrides = {})
154
+ return nil if component_name.nil?
155
+
156
+ component_name = component_name.to_sym
157
+
158
+ ComponentConfig.new(component_name, self).tap do |cfg|
159
+ cfg.client_config = overrides[:client_config] || {}
160
+ cfg.item_id = overrides[:item_id]
161
+
162
+ if respond_to?(:"#{component_name}_component")
163
+ send("#{component_name}_component", cfg)
164
+ elsif inline_components[component_name]
165
+ cfg.merge!(inline_components[component_name])
166
+ else
167
+ return nil
168
+ end
169
+
170
+ cfg.set_defaults!
171
+ end
144
172
  end
145
173
 
146
174
  # @return [Array<Class>] All component classes that we depend on (used to render all necessary javascripts and stylesheets)
147
175
  def dependency_classes
148
176
  res = []
149
177
 
150
- eagerly_loaded_components.keys.each do |aggr|
178
+ eagerly_loaded_components.each do |aggr|
151
179
  res += component_instance(aggr).dependency_classes
152
180
  end
153
181
 
@@ -156,36 +184,28 @@ module Netzke::Core
156
184
  end
157
185
 
158
186
  def extend_item(item)
159
- item = detect_and_normalize_component(item)
160
- components_in_config << item[:netzke_component] if include_component?(item)
161
- super item
187
+ super detect_and_normalize_component(item)
162
188
  end
163
189
 
164
190
  private
165
191
 
166
- def include_component?(cmp_config)
167
- cmp_config.is_a?(Hash) &&
168
- cmp_config[:netzke_component] &&
169
- cmp_config[:eager_loading] != false &&
170
- !cmp_config[:excluded]
171
- end
172
-
173
192
  def detect_and_normalize_component(item)
174
- item = {component: item} if item.is_a?(Symbol) && components[item]
175
- if item.is_a?(Hash) && component_name = item[:component]
176
- cfg = components[component_name]
177
- cfg.merge!(item)
178
- if cfg[:excluded]
179
- {excluded: true}
180
- else
181
- # cfg.merge(item).merge(netzke_component: item.delete(:component))
182
- item.merge(netzke_component: cfg[:component]) # TODO: TEST THIS
183
- end
184
- elsif item.is_a?(Hash) && (item[:klass] || item[:class_name])
185
- # declare component on the fly
186
- component_name = :"component_#{@implicit_component_index += 1}"
187
- components[component_name] = item.merge(eager_loading: true) unless item[:excluded]
188
- {netzke_component: component_name}
193
+ item = {component: item} if item.is_a?(Symbol) && respond_to?(:"#{item}_component")
194
+ return item unless item.is_a?(Hash)
195
+ return nil if item[:excluded]
196
+
197
+ if item[:klass] || item[:class_name]
198
+ # declared inline
199
+ item_id = item[:item_id] || :"component_#{@implicit_component_index}"
200
+ @implicit_component_index += 1
201
+ # components[item_id] = item
202
+ inline_components[item_id.to_sym] = item
203
+ @components_in_config << item_id
204
+ {netzke_component: item_id}
205
+ elsif item_id = item.delete(:component)
206
+ return nil if component_config(item_id)[:excluded]
207
+ @components_in_config << item_id
208
+ item.merge(netzke_component: item_id)
189
209
  else
190
210
  item
191
211
  end