netzke-core 0.7.7 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -2
- data/CHANGELOG.md +572 -0
- data/LICENSE +7 -1
- data/README.md +345 -29
- data/Rakefile +3 -3
- data/app/controllers/netzke_controller.rb +37 -48
- data/config/ci/before-travis.sh +3 -4
- data/javascripts/base.js +86 -150
- data/javascripts/ext.js +180 -210
- data/javascripts/{core_extensions.js → js_extensions.js} +0 -0
- data/lib/netzke-core.rb +16 -6
- data/lib/netzke/base.rb +84 -107
- data/lib/netzke/core.rb +7 -41
- data/lib/netzke/core/action_config.rb +37 -0
- data/lib/netzke/core/actions.rb +123 -0
- data/lib/netzke/core/client_class.rb +252 -0
- data/lib/netzke/core/component_config.rb +12 -0
- data/lib/netzke/core/composition.rb +274 -0
- data/lib/netzke/core/config_to_dsl_delegator.rb +69 -0
- data/lib/netzke/core/configuration.rb +63 -0
- data/lib/netzke/core/css_config.rb +51 -0
- data/lib/netzke/core/dynamic_assets.rb +19 -49
- data/lib/netzke/{embedding.rb → core/embedding.rb} +4 -6
- data/lib/netzke/core/endpoint_response.rb +15 -0
- data/lib/netzke/core/javascript.rb +111 -0
- data/lib/netzke/core/panel.rb +11 -0
- data/lib/netzke/{plugins.rb → core/plugins.rb} +10 -5
- data/lib/netzke/core/railz.rb +4 -0
- data/lib/netzke/{railz → core/railz}/action_view_ext.rb +22 -18
- data/lib/netzke/core/railz/action_view_ext/ext.rb +49 -0
- data/lib/netzke/core/railz/controller_extensions.rb +17 -0
- data/lib/netzke/core/railz/engine.rb +16 -0
- data/lib/netzke/core/railz/routes.rb +10 -0
- data/lib/netzke/core/ruby_ext.rb +5 -0
- data/lib/netzke/core/ruby_ext/array.rb +23 -0
- data/lib/netzke/core/ruby_ext/hash.rb +47 -0
- data/lib/netzke/{core_ext → core/ruby_ext}/string.rb +2 -7
- data/lib/netzke/core/ruby_ext/symbol.rb +13 -0
- data/lib/netzke/{core_ext → core/ruby_ext}/time_with_zone.rb +0 -0
- data/lib/netzke/core/services.rb +130 -0
- data/lib/netzke/core/session.rb +15 -19
- data/lib/netzke/core/state.rb +40 -0
- data/lib/netzke/core/stylesheets.rb +48 -0
- data/lib/netzke/core/version.rb +2 -2
- data/lib/netzke/plugin.rb +8 -11
- data/netzke-core.gemspec +69 -59
- data/test/core_test_app/Gemfile +2 -20
- data/test/core_test_app/Gemfile.lock +65 -74
- data/test/core_test_app/app/components/card_component_loader.rb +4 -4
- data/test/core_test_app/app/components/component_loader.rb +40 -120
- data/test/core_test_app/app/components/component_loader/javascripts/component_loader.js +49 -0
- data/test/core_test_app/app/components/component_with_actions.rb +61 -47
- data/test/core_test_app/app/components/component_with_custom_css.rb +8 -5
- data/test/core_test_app/app/components/component_with_js_mixin.rb +11 -5
- data/test/core_test_app/app/components/component_with_js_mixin/javascripts/extra_one.js +1 -1
- data/test/core_test_app/app/components/component_with_js_mixin/javascripts/extra_two.js +1 -1
- data/test/core_test_app/app/components/component_with_js_mixin/javascripts/method_set_one.js +1 -1
- data/test/core_test_app/app/components/component_with_nested_through.rb +2 -2
- data/test/core_test_app/app/components/component_with_prebuilt_toolbar_control.rb +12 -0
- data/test/core_test_app/app/components/component_with_prebuilt_toolbar_control/javascripts/component_with_prebuilt_toolbar_control.js +12 -0
- data/test/core_test_app/app/components/component_with_required_js.rb +24 -0
- data/test/core_test_app/app/components/configurable_on_class_level.rb +8 -0
- data/test/core_test_app/app/components/dsl_delegated_properties.rb +4 -0
- data/test/core_test_app/app/components/dsl_delegated_properties_base.rb +5 -0
- data/test/core_test_app/app/components/dynamic_tab_panel/javascripts/dynamic_tab_panel.js +2 -2
- data/test/core_test_app/app/components/ext_direct/composite.rb +32 -33
- data/test/core_test_app/app/components/ext_direct/details.rb +2 -4
- data/test/core_test_app/app/components/ext_direct/selector.rb +20 -22
- data/test/core_test_app/app/components/ext_direct/statistics.rb +2 -4
- data/test/core_test_app/app/components/extended_component_with_actions.rb +7 -3
- data/test/core_test_app/app/components/extended_component_with_js_mixin.rb +7 -4
- data/test/core_test_app/app/components/extended_component_with_js_mixin/javascripts/some_method_set.js +1 -1
- data/test/core_test_app/app/components/extended_server_caller.rb +20 -14
- data/test/core_test_app/app/components/hello_world.rb +23 -0
- data/test/core_test_app/app/components/hello_world/javascripts/hello_world.js +12 -0
- data/test/core_test_app/app/components/included.js +2 -2
- data/test/core_test_app/app/components/kinda_complex_component.rb +1 -3
- data/test/core_test_app/app/components/kinda_complex_component/basic_stuff.rb +15 -17
- data/test/core_test_app/app/components/kinda_complex_component/extra_stuff.rb +4 -5
- data/test/core_test_app/app/components/loader_of_component_with_custom_css.rb +14 -5
- data/test/core_test_app/app/components/localized_panel.rb +23 -25
- data/test/core_test_app/app/components/multipane_component_loader.rb +19 -20
- data/test/core_test_app/app/components/nested_component.rb +4 -5
- data/test/core_test_app/app/components/panel_with_plugin.rb +8 -3
- data/test/core_test_app/app/components/panel_with_tools.rb +15 -14
- data/test/core_test_app/app/components/plugin_with_components.rb +20 -12
- data/test/core_test_app/app/components/scoped_components/deep_scoped_components/some_deep_scoped_component.rb +5 -2
- data/test/core_test_app/app/components/scoped_components/extended_scoped_component.rb +5 -2
- data/test/core_test_app/app/components/scoped_components/some_scoped_component.rb +5 -2
- data/test/core_test_app/app/components/server_caller.rb +39 -17
- data/test/core_test_app/app/components/server_caller/javascripts/server_caller.js +42 -0
- data/test/core_test_app/app/components/server_counter.rb +18 -82
- data/test/core_test_app/app/components/server_counter/javascripts/server_counter.js +53 -0
- data/test/core_test_app/app/components/simple_authentication_component.rb +46 -0
- data/test/core_test_app/app/components/simple_component.rb +8 -3
- data/test/core_test_app/app/components/simple_composite.rb +12 -0
- data/test/core_test_app/app/components/simple_form_with_file_upload.rb +49 -0
- data/test/core_test_app/app/components/simple_panel.rb +2 -2
- data/test/core_test_app/app/components/simple_tab_panel.rb +24 -3
- data/test/core_test_app/app/components/simple_window.rb +4 -2
- data/test/core_test_app/app/components/some_composite.rb +77 -48
- data/test/core_test_app/app/components/some_plugin.rb +31 -30
- data/test/core_test_app/app/components/stateful_component.rb +46 -0
- data/test/core_test_app/app/components/stateful_component_with_shared_state.rb +11 -0
- data/test/core_test_app/app/components/window_with_simple_component.rb +14 -0
- data/test/core_test_app/app/views/layouts/application.html.erb +1 -1
- data/test/core_test_app/app/views/simple_rails/multiple_nested.html.erb +7 -19
- data/test/core_test_app/app/views/simple_rails/panel.html.erb +1 -0
- data/test/core_test_app/config/database.yml.travis +3 -5
- data/test/core_test_app/config/environments/production.rb +1 -1
- data/test/core_test_app/config/initializers/netzke.rb +3 -1
- data/test/core_test_app/config/locales/en.yml +9 -4
- data/test/core_test_app/config/locales/es.yml +4 -2
- data/test/core_test_app/config/routes.rb +2 -8
- data/test/core_test_app/db/schema.rb +3 -11
- data/test/core_test_app/features/actions_and_tools.feature +1 -0
- data/test/core_test_app/features/client-server.feature +7 -0
- data/test/core_test_app/features/component_loader.feature +13 -13
- data/test/core_test_app/features/composition.feature +14 -0
- data/test/core_test_app/features/config_to_dsl_delegation.feature +10 -0
- data/test/core_test_app/features/file_inclusion.feature +1 -1
- data/test/core_test_app/features/i18n.feature +4 -4
- data/test/core_test_app/features/js_include.feature +1 -1
- data/test/core_test_app/features/persistence.feature +21 -5
- data/test/core_test_app/features/step_definitions/generic_steps.rb +14 -0
- data/test/core_test_app/features/support/paths.rb +0 -3
- data/test/core_test_app/public/images/icons/accept.png +0 -0
- data/test/core_test_app/public/images/icons/anchor.png +0 -0
- data/test/core_test_app/public/images/icons/tick.png +0 -0
- data/test/core_test_app/spec/action_config_spec.rb +15 -0
- data/test/core_test_app/spec/{component/actions_spec.rb → actions_spec.rb} +38 -36
- data/test/core_test_app/spec/base_spec.rb +35 -0
- data/test/core_test_app/spec/client_class_spec.rb +17 -0
- data/test/core_test_app/spec/component +0 -0
- data/test/core_test_app/spec/composition_spec.rb +118 -0
- data/test/core_test_app/spec/core_ext_spec.rb +3 -14
- data/test/core_test_app/spec/endpoint_response_spec.rb +17 -0
- data/test/core_test_app/spec/javascript_spec.rb +33 -0
- data/test/core_test_app/spec/js_class_config_scope.rb +37 -0
- data/test/core_test_app/spec/panel_spec.rb +11 -0
- data/test/core_test_app/spec/services_spec.rb +16 -0
- data/test/core_test_app/spec/state_spec.rb +20 -0
- data/test/unit/core_ext_test.rb +0 -53
- data/test/unit/netzke_core_test.rb +11 -11
- metadata +76 -62
- data/CHANGELOG.rdoc +0 -325
- data/javascripts/touch.js +0 -58
- data/lib/netzke/actions.rb +0 -107
- data/lib/netzke/composition.rb +0 -224
- data/lib/netzke/config_to_dsl_delegator.rb +0 -43
- data/lib/netzke/configuration.rb +0 -195
- data/lib/netzke/core/masquerading.rb +0 -34
- data/lib/netzke/core_ext.rb +0 -6
- data/lib/netzke/core_ext/array.rb +0 -30
- data/lib/netzke/core_ext/hash.rb +0 -86
- data/lib/netzke/core_ext/symbol.rb +0 -21
- data/lib/netzke/ext_component.rb +0 -25
- data/lib/netzke/inheritance.rb +0 -31
- data/lib/netzke/javascript.rb +0 -382
- data/lib/netzke/javascript/scopes.rb +0 -39
- data/lib/netzke/railz.rb +0 -4
- data/lib/netzke/railz/action_view_ext/ext.rb +0 -64
- data/lib/netzke/railz/action_view_ext/touch.rb +0 -52
- data/lib/netzke/railz/controller_extensions.rb +0 -33
- data/lib/netzke/railz/engine.rb +0 -48
- data/lib/netzke/railz/routes.rb +0 -7
- data/lib/netzke/services.rb +0 -101
- data/lib/netzke/session.rb +0 -54
- data/lib/netzke/state.rb +0 -91
- data/lib/netzke/stylesheets.rb +0 -65
- data/test/core_test_app/app/components/component_with_included_js.rb +0 -16
- data/test/core_test_app/app/components/component_with_session_persistence.rb +0 -35
- data/test/core_test_app/app/components/deprecated/server_caller.rb +0 -20
- data/test/core_test_app/app/components/dynamic_tab_panel.rb +0 -19
- data/test/core_test_app/app/components/hello_world_component.rb +0 -31
- data/test/core_test_app/app/components/touch/hello_world_component.rb +0 -25
- data/test/core_test_app/app/components/touch/server_caller.rb +0 -28
- data/test/core_test_app/app/components/touch/simple_carousel.rb +0 -17
- data/test/core_test_app/app/controllers/touch_controller.rb +0 -6
- data/test/core_test_app/app/helpers/touch_helper.rb +0 -2
- data/test/core_test_app/app/views/layouts/touch.html.erb +0 -13
- data/test/core_test_app/db/migrate/20110110132720_create_netzke_component_states.rb +0 -20
- data/test/core_test_app/features/step_definitions/touch_steps.rb +0 -3
- data/test/core_test_app/features/touch.feature +0 -10
- data/test/core_test_app/gemfiles/rails3_1.gemfile +0 -16
- data/test/core_test_app/gemfiles/rails3_2.gemfile +0 -16
- data/test/core_test_app/spec/component/base_spec.rb +0 -36
- data/test/core_test_app/spec/component/component_spec.rb +0 -20
- data/test/core_test_app/spec/component/composition_spec.rb +0 -132
- data/test/core_test_app/spec/component/configuration_spec.rb +0 -61
- data/test/core_test_app/spec/component/javascript_spec.rb +0 -16
- data/test/core_test_app/spec/component/state_spec.rb +0 -18
@@ -0,0 +1,252 @@
|
|
1
|
+
module Netzke
|
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:
|
4
|
+
#
|
5
|
+
# class MyComponent < Netzke::Base
|
6
|
+
# js_configure do |c|
|
7
|
+
# c.extend = "Ext.form.Panel"
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
class ClientClass
|
11
|
+
attr_accessor :required_files, :base_class, :properties, :mixins, :translated_properties
|
12
|
+
|
13
|
+
def initialize(klass)
|
14
|
+
@klass = klass
|
15
|
+
@required_files = []
|
16
|
+
@mixins = []
|
17
|
+
@properties = {
|
18
|
+
extend: extended_class,
|
19
|
+
alias: class_alias,
|
20
|
+
}
|
21
|
+
@properties[:mixins] = ['Netzke.classes.Core.Mixin'] if extending_extjs_component?
|
22
|
+
@translated_properties = []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Allows assigning JavaScript prototype properties, including functions:
|
26
|
+
#
|
27
|
+
# class MyComponent < Netzke::Base
|
28
|
+
# js_configure do |c|
|
29
|
+
# # this will result in the +title+ property defined on the client class prototype
|
30
|
+
# c.title = "My cool component"
|
31
|
+
#
|
32
|
+
# # this will result in the +onButtonPress+ function defined on the client class prototype
|
33
|
+
# c.on_button_press = <<-JS
|
34
|
+
# function(){
|
35
|
+
# // ...
|
36
|
+
# }
|
37
|
+
# JS
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# An alternative way to define prototype properties is by using "mixins", see {ClientClass#mixin}
|
42
|
+
#
|
43
|
+
# Class attributes are accessible from inside +js_configure+:
|
44
|
+
#
|
45
|
+
# class MyComponent < Netzke::Base
|
46
|
+
# class_attribute :title
|
47
|
+
# self.title = "Some default title"
|
48
|
+
# js_configure do |c|
|
49
|
+
# c.title = self.title
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# Now you can configure your component on a class level like this:
|
54
|
+
#
|
55
|
+
# # e.g. in Rails initializers
|
56
|
+
# MyComponent.title = "New title for all MyComponents"
|
57
|
+
#
|
58
|
+
# Or using a helper method provided by Netzke:
|
59
|
+
#
|
60
|
+
# MyComponent.setup do |config|
|
61
|
+
# config.title = "New title for all MyComponents"
|
62
|
+
# end
|
63
|
+
def method_missing(name, *args)
|
64
|
+
if name =~ /(.+)=$/
|
65
|
+
value = args.first
|
66
|
+
@properties[$1.to_sym] = value.is_a?(String) && value =~ /^\s*function/ ? ActiveSupport::JSON::Variable.new(value) : value
|
67
|
+
else
|
68
|
+
@properties[name.to_sym]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Use it to specify JavaScript files to be loaded *before* this component's JavaScript code. Useful when using external extensions required by this component.
|
73
|
+
#
|
74
|
+
# It may accept one or more symbols or strings.
|
75
|
+
#
|
76
|
+
# Symbols will be expanded following a convention, e.g.:
|
77
|
+
#
|
78
|
+
# class MyComponent < Netzke::Base
|
79
|
+
# js_configure do |c|
|
80
|
+
# c.require :some_library
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# This will "require" a JavaScript file +{component_location}/my_component/javascripts/some_library.js+
|
85
|
+
#
|
86
|
+
# Strings will be interpreted as full paths to the required JavaScript file:
|
87
|
+
#
|
88
|
+
# js_configure do |c|
|
89
|
+
# c.require "#{File.dirname(__FILE__)}/my_component/one.js", "#{File.dirname(__FILE__)}/my_component/two.js"
|
90
|
+
# 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 }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Use it to "mixin" JavaScript objects defined in a separate file. It may accept one or more symbols or strings.
|
98
|
+
#
|
99
|
+
# Symbols will be expanded following a convention, e.g.:
|
100
|
+
#
|
101
|
+
# class MyComponent < Netzke::Base
|
102
|
+
# js_configure do |c|
|
103
|
+
# c.mixin :some_functionality
|
104
|
+
# #...
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
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:
|
109
|
+
#
|
110
|
+
# {
|
111
|
+
# someProperty: 100,
|
112
|
+
# someMethod: function(params){
|
113
|
+
# // ...
|
114
|
+
# }
|
115
|
+
# }
|
116
|
+
#
|
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_underscored.
|
119
|
+
#
|
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
|
+
@mixins << (a.is_a?(Symbol) ? expand_require_path(a, callr) : a)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Defines the "i18n" config property, that is a translation object for this component, such as:
|
130
|
+
# i18n: {
|
131
|
+
# overwriteConfirm: "Are you sure you want to overwrite preset '{0}'?",
|
132
|
+
# overwriteConfirmTitle: "Overwriting preset",
|
133
|
+
# deleteConfirm: "Are you sure you want to delete preset '{0}'?",
|
134
|
+
# deleteConfirmTitle: "Deleting preset"
|
135
|
+
# }
|
136
|
+
#
|
137
|
+
# E.g.:
|
138
|
+
#
|
139
|
+
# class MyComponent < Netzke::Base
|
140
|
+
# js_configure do |c|
|
141
|
+
# c.translate :overwrite_confirm, :overwrite_confirm_title, :delete_confirm, :delete_confirm_title
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
def translate(*args)
|
145
|
+
@translated_properties |= args
|
146
|
+
end
|
147
|
+
|
148
|
+
# The alias, required by Ext.Component, e.g.: widget.helloworld
|
149
|
+
def class_alias
|
150
|
+
[alias_prefix, xtype].join(".")
|
151
|
+
end
|
152
|
+
|
153
|
+
# Builds this component's xtype
|
154
|
+
# E.g.: netzkebasepackwindow, netzkebasepackgridpanel
|
155
|
+
def xtype
|
156
|
+
@klass.name.gsub("::", "").downcase
|
157
|
+
end
|
158
|
+
|
159
|
+
# 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
|
+
def class_code
|
162
|
+
res = []
|
163
|
+
# Defining the scope if it isn't known yet
|
164
|
+
res << %{Ext.ns("#{scope}");} unless scope == default_scope
|
165
|
+
|
166
|
+
res << class_declaration
|
167
|
+
|
168
|
+
# Store created class xtype in the cache
|
169
|
+
res << %(
|
170
|
+
Netzke.cache.push('#{xtype}');
|
171
|
+
)
|
172
|
+
|
173
|
+
res.join("\n")
|
174
|
+
end
|
175
|
+
|
176
|
+
# Top level scope which will be used to scope out Netzke classes
|
177
|
+
def default_scope
|
178
|
+
"Netzke.classes"
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns the scope of this component
|
182
|
+
# e.g. "Netzke.classes.Netzke.Basepack"
|
183
|
+
def scope
|
184
|
+
[default_scope, *@klass.name.split("::")[0..-2]].join(".")
|
185
|
+
end
|
186
|
+
|
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"
|
189
|
+
def class_name
|
190
|
+
[scope, @klass.name.split("::").last].join(".")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Whether we have to inherit from an Ext JS component, or a Netzke component
|
194
|
+
def extending_extjs_component?
|
195
|
+
@klass.superclass == Netzke::Base
|
196
|
+
end
|
197
|
+
|
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
|
+
# JavaScript code needed for this particulaer class. Includes external JS code and the JS class definition for self.
|
214
|
+
def code_with_dependencies
|
215
|
+
[required, class_code].join("\n")
|
216
|
+
end
|
217
|
+
|
218
|
+
# Generates declaration of the JS class as direct extension of a Ext component
|
219
|
+
def class_declaration
|
220
|
+
%(Ext.define('#{class_name}', #{properties_as_string});)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Alias prefix: 'widget' for components, 'plugin' for plugins
|
224
|
+
def alias_prefix
|
225
|
+
@klass < Netzke::Plugin ? "plugin" : "widget"
|
226
|
+
end
|
227
|
+
|
228
|
+
def mixins_as_string
|
229
|
+
mixins.presence && 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
|
+
def properties_as_string
|
238
|
+
[properties.to_nifty_json.chop, mixins_as_string].compact.join(",\n") + "}"
|
239
|
+
end
|
240
|
+
|
241
|
+
# Default extended class
|
242
|
+
def extended_class
|
243
|
+
extending_extjs_component? ? "Ext.panel.Panel" : @klass.superclass.js_config.class_name
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
def expand_require_path(sym, callr)
|
248
|
+
%Q(#{callr.split(".rb:").first}/javascripts/#{sym}.js)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Netzke::Core
|
2
|
+
class ComponentConfig < ActiveSupport::OrderedOptions
|
3
|
+
def initialize(name)
|
4
|
+
self.name = name.to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def set_defaults!
|
8
|
+
self.item_id ||= name # default item_id
|
9
|
+
self.klass ||= name.camelize.constantize # default klass
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
module Netzke::Core
|
2
|
+
# Any Netzke component can define child components, which can either be statically nested in the compound layout (e.g. as different regions of the 'border' layout), or dynamically loaded at a request (as is the advanced search panel in Basepack::GridPanel, for example).
|
3
|
+
#
|
4
|
+
# == Defining a component
|
5
|
+
#
|
6
|
+
# You can define a child component by calling the +component+ class method which normally requires a block:
|
7
|
+
#
|
8
|
+
# component :users do |c|
|
9
|
+
# c.klass = GridPanel
|
10
|
+
# c.model = "User"
|
11
|
+
# c.title = "Users"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# If no configuration is required, and the component's class name can be derived from its name, then the block can be omitted, e.g.:
|
15
|
+
#
|
16
|
+
# component :user_grid
|
17
|
+
#
|
18
|
+
# which is equivalent to:
|
19
|
+
#
|
20
|
+
# component :user_grid do |c|
|
21
|
+
# c.klass = UserGrid
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# == Overriding a component
|
25
|
+
#
|
26
|
+
# When overriding a component, the `super` method should be called, with the configuration object passed to it as parameter:
|
27
|
+
#
|
28
|
+
# component :users do |c|
|
29
|
+
# super(c)
|
30
|
+
# c.title = "Modified Title"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# == Referring to components in layouts
|
34
|
+
#
|
35
|
+
# A child component can be referred in the layout by using symbols:
|
36
|
+
#
|
37
|
+
# component :users do |c|
|
38
|
+
# c.title = "A Netzke component"
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def configure(c)
|
42
|
+
# super
|
43
|
+
# c.items = [
|
44
|
+
# { xtype: :panel, title: "Simple Ext panel" },
|
45
|
+
# :users # a Netzke component
|
46
|
+
# ]
|
47
|
+
# end
|
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):
|
50
|
+
#
|
51
|
+
# component :tab_one # ...
|
52
|
+
# component :tab_two # ...
|
53
|
+
#
|
54
|
+
# def configure(c)
|
55
|
+
# super
|
56
|
+
# c.items = [
|
57
|
+
# {component: :tab_one, title: "One"},
|
58
|
+
# {component: :tab_two, title: "Two"}
|
59
|
+
# ]
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# == Lazily vs eagerly loaded components
|
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:
|
65
|
+
#
|
66
|
+
# component :eagerly_loaded_window do |c|
|
67
|
+
# c.klass = SomeWindowComponent
|
68
|
+
# c.eager_loading = true
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# == Dynamic component loading
|
72
|
+
#
|
73
|
+
# Child components can be dynamically loaded by using client class' +netzkeLoadComponent+ method (see {javascript/ext.js}[https://github.com/netzke/netzke-core/blob/master/javascripts/ext.js] for inline documentation):
|
74
|
+
#
|
75
|
+
# == Excluded components
|
76
|
+
#
|
77
|
+
# 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
|
+
# This can be used for authorization.
|
79
|
+
module Composition
|
80
|
+
extend ActiveSupport::Concern
|
81
|
+
|
82
|
+
COMPONENT_METHOD_NAME = "%s_component"
|
83
|
+
|
84
|
+
|
85
|
+
included do
|
86
|
+
|
87
|
+
# Returns registered components
|
88
|
+
class_attribute :registered_components
|
89
|
+
self.registered_components = []
|
90
|
+
|
91
|
+
# @!method Foobar
|
92
|
+
# Loads a component on browser's request. Every Netzke component gets this endpoint.
|
93
|
+
# <tt>params</tt> should contain:
|
94
|
+
# * <tt>:cache</tt> - an array of component classes cached at the browser
|
95
|
+
# * <tt>:id</tt> - reference to the component
|
96
|
+
# * <tt>:container</tt> - Ext id of the container where in which the component will be rendered
|
97
|
+
endpoint :deliver_component do |params, this|
|
98
|
+
cache = params[:cache].split(",") # array of cached xtypes
|
99
|
+
component_name = params[:name].underscore.to_sym
|
100
|
+
component = components[component_name] && !components[component_name][:excluded] && component_instance(component_name)
|
101
|
+
|
102
|
+
if component
|
103
|
+
js, css = component.js_missing_code(cache), component.css_missing_code(cache)
|
104
|
+
this.netzke_eval_js(js) if js.present?
|
105
|
+
this.netzke_eval_css(css) if css.present?
|
106
|
+
|
107
|
+
this.netzke_component_delivered(component.js_config);
|
108
|
+
else
|
109
|
+
this.netzke_component_delivery_failed(component_name: component_name, msg: "Couldn't load component '#{component_name}'")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end # included
|
114
|
+
|
115
|
+
module ClassMethods
|
116
|
+
|
117
|
+
# Declares a child (nested) component.
|
118
|
+
# @param name [Symbol] component name
|
119
|
+
# @param block [Proc] config block
|
120
|
+
# @example
|
121
|
+
# component :users do |c|
|
122
|
+
# c.klass = Netzke::Basepack::Grid
|
123
|
+
# c.modul = "User"
|
124
|
+
# end
|
125
|
+
def component(name, &block)
|
126
|
+
self.registered_components |= [name]
|
127
|
+
|
128
|
+
method_name = COMPONENT_METHOD_NAME % name
|
129
|
+
if block_given?
|
130
|
+
define_method(method_name, &block)
|
131
|
+
else
|
132
|
+
define_method(method_name) do |component_config|
|
133
|
+
component_config
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Hash] component configs by name
|
140
|
+
def components
|
141
|
+
@components ||= self.class.registered_components.inject({}) do |out, name|
|
142
|
+
component_config = Netzke::Core::ComponentConfig.new(name)
|
143
|
+
send(COMPONENT_METHOD_NAME % name, component_config)
|
144
|
+
component_config.set_defaults!
|
145
|
+
if component_config.excluded
|
146
|
+
out.merge(name.to_sym => {excluded: true})
|
147
|
+
else
|
148
|
+
out.merge(name.to_sym => component_config)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [Hash] configs of eagerly loaded components by name
|
154
|
+
def eagerly_loaded_components
|
155
|
+
@eagerly_loaded_components ||= components.select{|k,v| components_in_config.include?(k) || v[:eager_loading]}
|
156
|
+
end
|
157
|
+
|
158
|
+
# @return [Array<Symbol>] components (by name) referred in config (and thus, required to be instantiated)
|
159
|
+
def components_in_config
|
160
|
+
@components_in_config || (normalize_config || true) && @components_in_config
|
161
|
+
end
|
162
|
+
|
163
|
+
# Called when the method_missing tries to processes a non-existing component. Override when needed.
|
164
|
+
def component_missing(aggr)
|
165
|
+
flash :error => "Unknown component #{aggr} for #{name}"
|
166
|
+
{:feedback => @flash}.to_nifty_json
|
167
|
+
end
|
168
|
+
|
169
|
+
# Recursively instantiates a child component based on its "path": e.g. if we have component :component1 which in its turn has component :component2, the path to the latter would be "component1__component2"
|
170
|
+
# @param name [Symbol] component name
|
171
|
+
def component_instance(name)
|
172
|
+
raise ArgumentError, "No component '#{name.inspect}' defined for '#{self.js_id}'" if !name.present?
|
173
|
+
|
174
|
+
@component_instance_cache ||= {}
|
175
|
+
@component_instance_cache[name] ||= begin
|
176
|
+
composite = self
|
177
|
+
name.to_s.split('__').each do |cmp|
|
178
|
+
cmp = cmp.to_sym
|
179
|
+
|
180
|
+
component_config = composite.components[cmp]
|
181
|
+
raise ArgumentError, "No component '#{cmp}' defined for '#{composite.js_id}'" if component_config.nil? || component_config[:excluded]
|
182
|
+
|
183
|
+
klass = component_config[:klass]
|
184
|
+
|
185
|
+
instance_config = component_config.merge(:name => cmp)
|
186
|
+
|
187
|
+
composite = klass.new(instance_config, composite) # params: config, parent
|
188
|
+
end
|
189
|
+
composite
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# @return [Array<Class>] All component classes that we depend on (used to render all necessary javascripts and stylesheets)
|
194
|
+
def dependency_classes
|
195
|
+
res = []
|
196
|
+
|
197
|
+
eagerly_loaded_components.keys.each do |aggr|
|
198
|
+
res += component_instance(aggr).dependency_classes
|
199
|
+
end
|
200
|
+
|
201
|
+
res += self.class.netzke_ancestors
|
202
|
+
res.uniq
|
203
|
+
end
|
204
|
+
|
205
|
+
# JS id of a component in the hierarchy, based on passed reference that follows the double-underscore notation. Referring to "parent" is allowed. If going to far up the hierarchy will result in <tt>nil</tt>, while referring to a non-existent component will simply provide an erroneous ID.
|
206
|
+
# For example:
|
207
|
+
# <tt>parent__parent__child__subchild</tt> will traverse the hierarchy 2 levels up, then going down to "child", and further to "subchild". If such a component exists in the hierarchy, its global id will be returned, otherwise <tt>nil</tt> will be returned.
|
208
|
+
# @param ref [Symbol] reference to a child component
|
209
|
+
# @return [String] JS id
|
210
|
+
def js_id_by_reference(ref)
|
211
|
+
ref = ref.to_s
|
212
|
+
return parent && parent.js_id if ref == "parent"
|
213
|
+
substr = ref.sub(/^parent__/, "")
|
214
|
+
if substr == ref # there's no "parent__" in the beginning
|
215
|
+
return js_id + "__" + ref
|
216
|
+
else
|
217
|
+
return parent.js_id_by_reference(substr)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
protected
|
222
|
+
|
223
|
+
# During normalization of the config object, this method is being called with each item found (recursively) in there.
|
224
|
+
# For example, symbols representing nested child components get replaced with a proper config hash. Same goes for actions.
|
225
|
+
# Override to do any additional checks/enhancements. See, for example, +Netzke::Basepack::WrapLazyLoaded+.
|
226
|
+
# @return [Object] extended item
|
227
|
+
def extend_item(item)
|
228
|
+
# in a situation of action and component being equally named, action will take precedence
|
229
|
+
|
230
|
+
if item.is_a?(Symbol) && item_config = actions[item]
|
231
|
+
item = {netzke_action: item}
|
232
|
+
elsif item.is_a?(Symbol) && item_config = components[item]
|
233
|
+
item = {netzke_component: item}
|
234
|
+
end
|
235
|
+
|
236
|
+
item[:excluded] = true if item_config && item_config[:excluded]
|
237
|
+
|
238
|
+
if item.is_a?(Hash)
|
239
|
+
return nil if item[:excluded] # it'll get compacted away by Array#deep_map
|
240
|
+
|
241
|
+
# replace the `component` and `action` keys with `netzke_component` and `netzke_action`, which will be looked for at the JS side
|
242
|
+
item[:netzke_action] = item.delete(:action) if item[:action]
|
243
|
+
item[:netzke_component] = item.delete(:component) if item[:component]
|
244
|
+
|
245
|
+
@components_in_config << item[:netzke_component] if item[:netzke_component] && item[:eager_loading] != false
|
246
|
+
end
|
247
|
+
|
248
|
+
item
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# We'll build a couple of useful instance variables here:
|
254
|
+
#
|
255
|
+
# +components_in_config+ - an array of components (by name) referred in items
|
256
|
+
# +normalized_config+ - a config that has all the config extensions applied
|
257
|
+
def normalize_config
|
258
|
+
@components_in_config = []
|
259
|
+
@normalized_config = config.dup.tap do |c|
|
260
|
+
c.each_pair do |k,v|
|
261
|
+
c.delete(k) if self.class.server_side_config_options.include?(k.to_sym)
|
262
|
+
c[k] = v.deep_map{|el| extend_item(el)} if v.is_a?(Array)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# @return [Hash] config with all placeholders (like child components referred by symbols) expanded
|
268
|
+
def normalized_config
|
269
|
+
# make sure we call normalize_config first
|
270
|
+
@normalized_config || (normalize_config || true) && @normalized_config
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|