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.
- data/CHANGELOG.md +65 -607
- data/Gemfile +1 -1
- data/LICENSE +2 -6
- data/README.md +77 -145
- data/javascripts/base.js +582 -96
- data/javascripts/core.js +62 -0
- data/javascripts/notifications.js +43 -0
- data/javascripts/remoting_provider.js +29 -0
- data/javascripts/routing.js +63 -0
- data/lib/netzke/base.rb +16 -83
- data/lib/netzke/core/action_config.rb +1 -5
- data/lib/netzke/core/actions.rb +59 -21
- data/lib/netzke/core/{client_class.rb → client_class_config.rb} +81 -73
- data/lib/netzke/core/client_code.rb +157 -0
- data/lib/netzke/core/component_config.rb +2 -2
- data/lib/netzke/core/composition.rb +85 -65
- data/lib/netzke/core/configuration.rb +26 -14
- data/lib/netzke/core/core_i18n.rb +17 -0
- data/lib/netzke/core/css_config.rb +6 -6
- data/lib/netzke/core/dynamic_assets.rb +17 -24
- data/lib/netzke/core/embedding.rb +2 -2
- data/lib/netzke/core/endpoint_response.rb +8 -2
- data/lib/netzke/core/inheritance.rb +33 -0
- data/lib/netzke/core/plugins.rb +1 -4
- data/lib/netzke/core/railz/action_view_ext.rb +1 -1
- data/lib/netzke/core/railz/controller_extensions.rb +21 -15
- data/lib/netzke/core/services.rb +61 -48
- data/lib/netzke/core/session.rb +0 -2
- data/lib/netzke/core/state.rb +11 -9
- data/lib/netzke/core/stylesheets.rb +3 -3
- data/lib/netzke/core/version.rb +1 -1
- data/lib/netzke/core.rb +3 -3
- data/lib/netzke/plugin.rb +2 -6
- data/stylesheets/core.css +2 -2
- metadata +11 -10
- data/TODO.md +0 -9
- data/javascripts/ext.js +0 -518
- data/lib/netzke/core/config_to_dsl_delegator.rb +0 -62
- data/lib/netzke/core/dsl_support.rb +0 -70
- data/lib/netzke/core/html.rb +0 -29
- 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 `
|
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
|
-
#
|
6
|
+
# client_class do |c|
|
7
7
|
# c.extend = "Ext.form.Panel"
|
8
8
|
# end
|
9
9
|
# end
|
10
|
-
class
|
11
|
-
attr_accessor :
|
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
|
-
@
|
16
|
-
@
|
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.
|
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
|
-
#
|
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
|
42
|
+
# An alternative way to define prototype properties is by using {ClientClassConfig#include}
|
42
43
|
#
|
43
|
-
#
|
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
|
-
#
|
49
|
+
# client_class do |c|
|
49
50
|
# c.title = self.title
|
50
51
|
# end
|
51
52
|
# end
|
52
53
|
#
|
53
|
-
#
|
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
|
-
#
|
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/
|
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
|
-
#
|
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(*
|
92
|
-
|
93
|
-
|
94
|
-
|
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 "
|
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
|
-
#
|
103
|
-
# c.
|
104
|
+
# client_class do |c|
|
105
|
+
# c.include :some_functionality
|
104
106
|
# #...
|
105
107
|
# end
|
106
108
|
# end
|
107
109
|
#
|
108
|
-
# This will "
|
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
|
-
#
|
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 {
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@
|
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
|
-
#
|
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
|
-
|
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.
|
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
|
-
#
|
177
|
+
# Additional scope for your Netzke components.
|
177
178
|
def default_scope
|
178
|
-
"
|
179
|
+
""
|
179
180
|
end
|
180
181
|
|
181
182
|
# Returns the scope of this component
|
182
|
-
# e.g. "Netzke.
|
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
|
188
|
-
# E.g.: "Netzke.
|
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
|
-
[
|
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
|
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.
|
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
|
-
|
247
|
-
|
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
|
10
|
-
self.klass ||= self.class_name.try(:constantize) || name.camelize.constantize
|
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
|
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::
|
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
|
-
#
|
84
|
-
|
107
|
+
# Hash of components declared inline in the config
|
108
|
+
attr_accessor :inline_components
|
85
109
|
|
86
|
-
|
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
|
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 =
|
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
|
-
|
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
|
-
|
130
|
+
{ error: "Couldn't load component '#{component_name}'" }
|
111
131
|
end
|
112
132
|
end
|
113
|
-
|
114
133
|
end # included
|
115
134
|
|
116
|
-
# @return [
|
135
|
+
# @return [Array] names of eagerly loaded components
|
117
136
|
def eagerly_loaded_components
|
118
|
-
|
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
|
125
|
-
def component_instance(
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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.
|
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
|
-
|
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) &&
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|