netzke-core 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/.autotest +1 -1
  2. data/.gitignore +4 -2
  3. data/CHANGELOG.rdoc +37 -15
  4. data/README.rdoc +93 -28
  5. data/Rakefile +5 -7
  6. data/TODO +24 -3
  7. data/app/controllers/netzke_controller.rb +70 -0
  8. data/app/models/netzke_preference.rb +170 -0
  9. data/autotest/discover.rb +2 -3
  10. data/features/actions.feature +13 -0
  11. data/features/basic.feature +7 -0
  12. data/features/client-server.feature +15 -0
  13. data/features/complex_component.feature +18 -0
  14. data/features/component.feature +13 -0
  15. data/features/component_loader.feature +31 -0
  16. data/features/composition.feature +32 -0
  17. data/features/custom_css.feature +17 -0
  18. data/features/file_inclusion.feature +13 -0
  19. data/features/inheritance.feature +19 -0
  20. data/features/persistence.feature +16 -0
  21. data/features/scopes.feature +17 -0
  22. data/features/step_definitions/custom_css_steps.rb +7 -0
  23. data/features/step_definitions/generic_steps.rb +15 -0
  24. data/features/step_definitions/web_steps.rb +219 -0
  25. data/features/support/env.rb +62 -0
  26. data/features/support/paths.rb +45 -0
  27. data/generators/netzke_core/USAGE +3 -3
  28. data/generators/netzke_core/netzke_core_generator.rb +8 -0
  29. data/install.rb +1 -1
  30. data/javascripts/core.js +542 -499
  31. data/lib/generators/migration_helper.rb +32 -0
  32. data/lib/generators/netzke/USAGE +9 -0
  33. data/lib/generators/netzke/core_generator.rb +24 -0
  34. data/lib/netzke/actions.rb +102 -0
  35. data/lib/netzke/base.rb +77 -529
  36. data/lib/netzke/composition.rb +251 -0
  37. data/lib/netzke/configuration.rb +134 -0
  38. data/lib/netzke/core/masquerading.rb +34 -0
  39. data/lib/netzke/core/session.rb +30 -0
  40. data/lib/netzke/core/version.rb +11 -0
  41. data/lib/netzke/core.rb +53 -0
  42. data/lib/netzke/core_ext/array.rb +30 -0
  43. data/lib/netzke/core_ext/hash.rb +84 -0
  44. data/lib/netzke/core_ext/string.rb +26 -0
  45. data/lib/netzke/core_ext/symbol.rb +13 -0
  46. data/lib/netzke/core_ext/time_with_zone.rb +7 -0
  47. data/lib/netzke/core_ext.rb +5 -141
  48. data/lib/netzke/embedding.rb +21 -0
  49. data/lib/netzke/ext_component.rb +25 -0
  50. data/lib/netzke/javascript.rb +248 -0
  51. data/lib/netzke/persistence.rb +118 -0
  52. data/lib/netzke/rails/action_view_ext.rb +103 -0
  53. data/lib/netzke/{controller_extensions.rb → rails/controller_extensions.rb} +7 -2
  54. data/lib/netzke/rails/routes.rb +7 -0
  55. data/lib/netzke/services.rb +68 -0
  56. data/lib/netzke/session.rb +35 -0
  57. data/lib/netzke/stylesheets.rb +52 -0
  58. data/lib/netzke-core.rb +28 -29
  59. data/netzke-core.gemspec +253 -0
  60. data/spec/component/actions_spec.rb +94 -0
  61. data/spec/component/base_spec.rb +25 -0
  62. data/spec/component/composition_spec.rb +132 -0
  63. data/spec/component/javascript_spec.rb +15 -0
  64. data/spec/core_ext_spec.rb +16 -0
  65. data/spec/spec.opt +2 -0
  66. data/spec/spec_helper.rb +35 -0
  67. data/{test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb → templates/core/create_netzke_preferences.rb} +4 -4
  68. data/test/rails_app/.gitignore +4 -0
  69. data/test/rails_app/Gemfile +36 -0
  70. data/test/rails_app/Gemfile.lock +137 -0
  71. data/test/rails_app/README +15 -0
  72. data/test/rails_app/Rakefile +7 -0
  73. data/test/rails_app/app/components/border_layout_panel.rb +4 -0
  74. data/test/rails_app/app/components/component_loader.rb +59 -0
  75. data/test/rails_app/app/components/component_with_actions.rb +58 -0
  76. data/test/rails_app/app/components/component_with_custom_css.rb +8 -0
  77. data/test/rails_app/app/components/component_with_included_js.rb +16 -0
  78. data/test/rails_app/app/components/component_with_session_persistence.rb +25 -0
  79. data/test/rails_app/app/components/custom.css +3 -0
  80. data/test/rails_app/app/components/deprecated/server_caller.rb +20 -0
  81. data/test/rails_app/app/components/extended_component_with_actions.rb +5 -0
  82. data/test/rails_app/app/components/extended_server_caller.rb +18 -0
  83. data/test/rails_app/app/components/included.js +5 -0
  84. data/test/rails_app/app/components/kinda_complex_component/basic_stuff.rb +35 -0
  85. data/test/rails_app/app/components/kinda_complex_component/extra_stuff.rb +16 -0
  86. data/test/rails_app/app/components/kinda_complex_component.rb +7 -0
  87. data/test/rails_app/app/components/loader_of_component_with_custom_css.rb +15 -0
  88. data/test/rails_app/app/components/scoped_components/deep_scoped_components/some_deep_scoped_component.rb +7 -0
  89. data/test/rails_app/app/components/scoped_components/extended_scoped_component.rb +5 -0
  90. data/test/rails_app/app/components/scoped_components/some_scoped_component.rb +5 -0
  91. data/test/rails_app/app/components/server_caller.rb +21 -0
  92. data/test/rails_app/app/components/simple_component.rb +5 -0
  93. data/test/rails_app/app/components/simple_tab_panel.rb +27 -0
  94. data/test/rails_app/app/components/simple_window.rb +3 -0
  95. data/test/rails_app/app/components/some_composite.rb +65 -0
  96. data/test/rails_app/app/components/some_ext_component.rb +8 -0
  97. data/test/{app_root → rails_app}/app/controllers/application_controller.rb +1 -0
  98. data/test/rails_app/app/controllers/components_controller.rb +12 -0
  99. data/test/rails_app/app/controllers/multiple_components_controller.rb +2 -0
  100. data/test/rails_app/app/controllers/welcome_controller.rb +5 -0
  101. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  102. data/test/rails_app/app/views/layouts/application.html.erb +13 -0
  103. data/test/rails_app/app/views/multiple_components/set_one.html.erb +3 -0
  104. data/test/rails_app/config/application.rb +45 -0
  105. data/test/rails_app/config/boot.rb +13 -0
  106. data/test/rails_app/config/database.yml +22 -0
  107. data/test/rails_app/config/environment.rb +6 -0
  108. data/test/rails_app/config/environments/development.rb +22 -0
  109. data/test/rails_app/config/environments/production.rb +49 -0
  110. data/test/rails_app/config/environments/test.rb +35 -0
  111. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  112. data/test/rails_app/config/initializers/inflections.rb +10 -0
  113. data/test/rails_app/config/initializers/mime_types.rb +5 -0
  114. data/test/rails_app/config/initializers/netzke.rb +3 -0
  115. data/test/rails_app/config/initializers/secret_token.rb +7 -0
  116. data/test/rails_app/config/initializers/session_store.rb +8 -0
  117. data/test/rails_app/config/locales/en.yml +5 -0
  118. data/test/rails_app/config/routes.rb +64 -0
  119. data/test/rails_app/config.ru +4 -0
  120. data/test/rails_app/db/development_structure.sql +4 -0
  121. data/{generators/netzke_core/templates/create_netzke_preferences.rb → test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb} +1 -3
  122. data/test/rails_app/db/schema.rb +24 -0
  123. data/test/rails_app/db/seeds.rb +7 -0
  124. data/test/{app_root/config/environments/in_memory.rb → rails_app/lib/tasks/.gitkeep} +0 -0
  125. data/test/rails_app/public/404.html +26 -0
  126. data/test/rails_app/public/422.html +26 -0
  127. data/test/rails_app/public/500.html +26 -0
  128. data/test/{app_root/config/environments/mysql.rb → rails_app/public/favicon.ico} +0 -0
  129. data/test/rails_app/public/images/rails.png +0 -0
  130. data/test/rails_app/public/robots.txt +5 -0
  131. data/test/rails_app/script/rails +6 -0
  132. data/test/{app_root/config/environments/postgresql.rb → rails_app/vendor/plugins/.gitkeep} +0 -0
  133. data/test/unit/netzke_core_test.rb +74 -75
  134. data/test/unit/netzke_preference_test.rb +2 -2
  135. metadata +176 -48
  136. data/lib/app/controllers/netzke_controller.rb +0 -46
  137. data/lib/app/models/netzke_preference.rb +0 -171
  138. data/lib/netzke/action_view_ext.rb +0 -81
  139. data/lib/netzke/base_js.rb +0 -339
  140. data/lib/netzke/routing.rb +0 -9
  141. data/test/app_root/app/models/role.rb +0 -3
  142. data/test/app_root/app/models/user.rb +0 -3
  143. data/test/app_root/config/boot.rb +0 -115
  144. data/test/app_root/config/database.yml +0 -31
  145. data/test/app_root/config/environment.rb +0 -14
  146. data/test/app_root/config/environments/sqlite.rb +0 -0
  147. data/test/app_root/config/environments/sqlite3.rb +0 -0
  148. data/test/app_root/config/routes.rb +0 -4
  149. data/test/app_root/db/migrate/20090423214303_create_roles.rb +0 -11
  150. data/test/app_root/db/migrate/20090423222114_create_users.rb +0 -12
  151. data/test/app_root/lib/console_with_fixtures.rb +0 -4
  152. data/test/app_root/script/console +0 -7
data/lib/netzke/base.rb CHANGED
@@ -1,28 +1,36 @@
1
- require 'netzke/base_js'
1
+ require 'active_support/core_ext'
2
+ require 'netzke/core_ext'
3
+ require 'netzke/javascript'
4
+ require 'netzke/stylesheets'
5
+ require 'netzke/services'
6
+ require 'netzke/composition'
7
+ require 'netzke/configuration'
8
+ require 'netzke/persistence'
9
+ require 'netzke/embedding'
10
+ require 'netzke/actions'
11
+ require 'netzke/session'
2
12
 
3
13
  module Netzke
4
14
  # = Base
5
- # Base class for every Netzke widget
15
+ # Base class for every Netzke component
6
16
  #
7
- # To instantiate a widget in the controller:
17
+ # To instantiate a component in the controller:
8
18
  #
9
- # netzke :widget_name, configuration_hash
19
+ # netzke :component_name, configuration_hash
10
20
  #
11
21
  # == Configuration
12
- # * <tt>:class_name</tt> - name of the widget class in the scope of the Netzke module, e.g. "FormPanel".
13
- # When a widget is defined in the controller and this option is omitted, widget class is deferred from the widget's
22
+ # * <tt>:class_name</tt> - name of the component class in the scope of the Netzke module, e.g. "FormPanel".
23
+ # When a component is defined in the controller and this option is omitted, component class is deferred from the component's
14
24
  # name. E.g.:
15
25
  #
16
26
  # netzke :grid_panel, :model => "User"
17
27
  #
18
28
  # In this case <tt>:class_name</tt> is assumed to be "GridPanel"
19
29
  #
20
- # * <tt>:ext_config</tt> - a config hash that is used to create a javascript instance of the widget. Every
21
- # configuration that comes here will be available inside the javascript instance of the widget.
22
- # * <tt>:persistent_config</tt> - if set to <tt>true</tt>, the widget will use persistent storage to store its state;
30
+ # * <tt>:persistent_config</tt> - if set to <tt>true</tt>, the component will use persistent storage to store its state;
23
31
  # for instance, Netzke::GridPanel stores there its columns state (width, visibility, order, headers, etc).
24
- # A widget may or may not provide interface to its persistent settings. GridPanel and FormPanel from netzke-basepack
25
- # are examples of widgets that by default do.
32
+ # A component may or may not provide interface to its persistent settings. GridPanel and FormPanel from netzke-basepack
33
+ # are examples of components that by default do.
26
34
  #
27
35
  # Examples of configuration:
28
36
  #
@@ -30,565 +38,105 @@ module Netzke
30
38
  # :class_name => "GridPanel",
31
39
  # :model => "Book", # GridPanel specific option
32
40
  # :persistent_config => false, # don't use persistent config for this instance
33
- # :ext_config => {
34
- # :icon_cls => 'icon-grid',
35
- # :title => "My books"
36
- # }
41
+ # :icon_cls => 'icon-grid',
42
+ # :title => "My books"
37
43
  #
38
44
  # netzke :form_panel,
39
45
  # :model => "User" # FormPanel specific option
40
46
  class Base
41
- extend ActiveSupport::Memoizable
42
47
 
43
- include Netzke::BaseJs # javascript (client-side)
44
-
45
- attr_accessor :parent, :name, :global_id #, :permissions, :session
46
-
47
- # Class-level Netzke::Base configuration. The defaults also get specified here.
48
- def self.config
49
- set_default_config({
50
- # Set to true, if you want generated JS to be minified
51
- :minify_js => Rails.env.production?,
52
- # Which javascripts and stylesheets must get included at the initial load (see netzke-core.rb)
53
- :javascripts => [],
54
- :stylesheets => [],
55
-
56
- :external_css => [],
57
-
58
- # AR model that provides us with persistent config functionality
59
- :persistent_config_manager => "NetzkePreference",
60
-
61
- # Default location of extjs library
62
- :ext_location => defined?(RAILS_ROOT) && "#{RAILS_ROOT}/public/extjs",
63
-
64
- # Default location of icons, relative to the root of the domain
65
- :icons_uri => ActionController::Base.relative_url_root.to_s+"/images/icons/" ,
66
-
67
- # Default instance config
68
- :default_config => {
69
- :persistent_config => true
70
- }
71
- })
72
- end
73
-
74
- def self.set_default_config(c) #:nodoc:
75
- @@config ||= {}
76
- @@config[self.name] ||= c
77
- end
78
-
79
- # Override class-level defaults specified in <tt>Netzke::Base.config</tt>.
80
- # E.g. in config/initializers/netzke-config.rb:
81
- #
82
- # Netzke::GridPanel.configure :default_config => {:persistent_config => true}
83
- # Netzke::GridPanel.configure :column_filters_available, true
84
- def self.configure(*args)
85
- if args.first.is_a?(Symbol)
86
- config[args.first] = args.last
87
- else
88
- # first arg is hash
89
- config.deep_merge!(args.first)
90
- end
91
- # widget may implement some kind of control for configuration consistency
92
- enforce_config_consistency if respond_to?(:enforce_config_consistency)
93
- end
48
+ class_attribute :default_instance_config
49
+ self.default_instance_config = {}
94
50
 
95
- # Short widget class name, e.g.:
96
- # Netzke::Module::SomeWidget => Module::SomeWidget
97
- def self.short_widget_class_name
98
- self.name.sub(/^Netzke::/, "")
99
- end
100
-
101
- # Access to controller sessions
102
- def self.session
103
- @@session ||= {}
104
- end
51
+ include Session
52
+ include Persistence
53
+ include Configuration
54
+ include Javascript
55
+ include Services
56
+ include Composition
57
+ include Stylesheets
58
+ include Embedding
59
+ include Actions
105
60
 
106
- def self.session=(s)
107
- @@session = s
108
- end
109
-
110
- # Should be called by session controller at the moment of successfull login
111
- def self.login
112
- session[:_netzke_next_request_is_first_after_login] = true
113
- end
114
-
115
- # Should be called by session controller at the moment of logout
116
- def self.logout
117
- session[:_netzke_next_request_is_first_after_logout] = true
118
- end
119
-
120
- # Declare connection points between client side of a widget and its server side. For example:
121
- #
122
- # api :reset_data
123
- #
124
- # will provide JavaScript side with a method <tt>resetData</tt> that will result in a call to Ruby
125
- # method <tt>reset_data</tt>, e.g.:
126
- #
127
- # this.resetData({hard:true});
128
- #
129
- # See netzke-basepack's GridPanel for an example.
130
- def self.api(*api_points)
131
- apip = read_inheritable_attribute(:api_points) || []
132
- api_points.each{|p| apip << p}
133
- write_inheritable_attribute(:api_points, apip)
134
-
135
- # It may be needed later for security
136
- api_points.each do |apip|
137
- module_eval <<-END, __FILE__, __LINE__
138
- def api_#{apip}(*args)
139
- before_api_call_result = defined?(before_api_call) && before_api_call('#{apip}', *args) || {}
140
- (before_api_call_result.empty? ? #{apip}(*args) : before_api_call_result).to_nifty_json
141
- end
142
- # FIXME: commented out because otherwise ColumnOperations stop working
143
- # def #{apip}(*args)
144
- # flash :warning => "API point '#{apip}' is not implemented for widget '#{short_widget_class_name}'"
145
- # {:flash => @flash}
146
- # end
147
- END
148
- end
149
- end
150
-
151
- api :load_aggregatee_with_cache # every widget gets this api
152
-
153
- # Array of API-points specified with <tt>Netzke::Base.api</tt> method
154
- def self.api_points
155
- read_inheritable_attribute(:api_points)
156
- end
157
-
158
- # Instance of widget by config
159
- def self.instance_by_config(config)
160
- ::ActiveSupport::Deprecation.warn("widget_class_name option is deprecated. Use class_name instead", caller) if config[:widget_class_name]
161
- widget_class = "Netzke::#{config[:class_name] || config[:class_name]}".constantize
162
- widget_class.new(config)
163
- end
164
-
165
- # Persistent config manager class
166
- def self.persistent_config_manager_class
167
- Netzke::Base.config[:persistent_config_manager].try(:constantize)
168
- rescue NameError
169
- nil
170
- end
171
-
172
- # Example:
173
- # masquarade_as(:role, 2)
174
- # masquarade_as(:user, 4)
175
- # masquarade_as(:world)
176
- def self.masquerade_as(authority_level, authority_id = true)
177
- reset_masquerading
178
- session.merge!(:"masq_#{authority_level}" => authority_id)
179
- end
180
-
181
- def self.reset_masquerading
182
- session[:masq_world] = session[:masq_role] = session[:masq_user] = nil
183
- end
184
-
185
- # Who are we acting as?
186
- def self.authority_level
187
- if session[:masq_world]
188
- :world
189
- elsif session[:masq_role]
190
- [:role, session[:masq_role]]
191
- elsif session[:masq_user]
192
- [:user, session[:masq_user]]
193
- elsif session[:netzke_user_id]
194
- [:self, session[:netzke_user_id]]
195
- else
196
- :none # or nil ?
197
- end
198
- end
61
+ attr_accessor :parent, :name, :global_id #, :permissions, :session
199
62
 
200
- # Widget initialization process
201
- # * the config hash is available to the widget after the "super" call in the initializer
63
+ # Component initialization process
64
+ # * the config hash is available to the component after the "super" call in the initializer
202
65
  # * override/add new default configuration options into the "default_config" method
203
66
  # (the config hash is not yet available)
204
- def initialize(config = {}, parent = nil)
205
- # @session = Netzke::Base.session
206
- @passed_config = config # configuration passed at the moment of instantiation
67
+ def initialize(conf = {}, parent = nil)
68
+ @passed_config = conf # configuration passed at the moment of instantiation
69
+ @passed_config.deep_freeze
207
70
  @parent = parent
208
- @name = config[:name].nil? ? short_widget_class_name.underscore : config[:name].to_s
71
+ @name = conf[:name].nil? ? short_component_class_name.underscore : conf[:name].to_s
209
72
  @global_id = parent.nil? ? @name : "#{parent.global_id}__#{@name}"
210
73
  @flash = []
211
- end
212
-
213
- def session
214
- Netzke::Base.session
215
- end
216
-
217
- #
218
- # Configuration
219
- #
220
-
221
- # Default config - before applying any passed configuration
222
- def default_config
223
- self.class.config[:default_config].nil? ? {} : {}.merge(self.class.config[:default_config])
224
- end
225
-
226
- # Static, hardcoded config. Consists of default values merged with config that was passed during instantiation
227
- def initial_config
228
- default_config.deep_merge(@passed_config)
229
- end
230
- memoize :initial_config
231
-
232
- # Config that is not overwritten by parents and sessions
233
- def independent_config
234
- initial_config.deep_merge(persistent_config_hash)
235
- end
236
- memoize :independent_config
237
-
238
- # If the widget has persistent config in its disposal
239
- def persistent_config_enabled?
240
- !persistent_config_manager_class.nil? && initial_config[:persistent_config]
241
- end
242
-
243
- # Access to own persistent config, e.g.:
244
- # persistent_config["window.size"] = 100
245
- # persistent_config["window.size"] => 100
246
- # This method is user/role-aware
247
- def persistent_config
248
- if persistent_config_enabled?
249
- config_class = self.class.persistent_config_manager_class
250
- config_class.widget_name = persistence_key.to_s # pass to the config class our unique name
251
- config_class
252
- else
253
- # if we can't use presistent config, all the calls to it will always return nil,
254
- # and the "="-operation will be ignored
255
- logger.debug "==> NETZKE: no persistent config is set up for widget '#{global_id}'"
256
- {}
257
- end
258
- end
259
-
260
- # Access to the global persistent config (e.g. of another widget)
261
- def global_persistent_config(owner = nil)
262
- config_class = self.class.persistent_config_manager_class
263
- config_class.widget_name = owner
264
- config_class
265
- end
266
-
267
- # A string which will identify NetzkePreference records for this widget.
268
- # If <tt>persistence_key</tt> is passed, use it. Otherwise use global widget's id.
269
- def persistence_key #:nodoc:
270
- # initial_config[:persistence_key] ? parent.try(:persistence_key) ? "#{parent.persistence_key}__#{initial_config[:persistence_key]}".to_sym : initial_config[:persistence_key] : global_id.to_sym
271
- initial_config[:persistence_key] ? initial_config[:persistence_key] : global_id.to_sym
272
- end
273
-
274
- # Update persistent_config[:ext_config] hash
275
- def update_persistent_ext_config(hsh)
276
- current_config = persistent_config[:ext_config] || {}
277
- current_config.deep_merge!(hsh.deep_convert_keys{ |k| k.to_s }) # first, recursively stringify the keys
278
- persistent_config[:ext_config] = current_config
279
- end
280
-
281
- # Resulting config that takes into account all possible ways to configure a widget. *Read only*.
282
- # Translates into something like this:
283
- # default_config.
284
- # deep_merge(@passed_config).
285
- # deep_merge(persistent_config_hash).
286
- # deep_merge(strong_parent_config).
287
- # deep_merge(strong_session_config)
288
- def config
289
- independent_config.deep_merge(strong_parent_config).deep_merge(strong_session_config)
290
- end
291
- memoize :config
292
-
293
- def flat_config(key = nil)
294
- fc = config.flatten_with_type
295
- key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
296
- end
297
-
298
- def strong_parent_config
299
- @strong_parent_config ||= parent.nil? ? {} : parent.strong_children_config
300
- end
301
-
302
- def flat_independent_config(key = nil)
303
- fc = independent_config.flatten_with_type
304
- key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
305
- end
306
-
307
- def flat_default_config(key = nil)
308
- fc = default_config.flatten_with_type
309
- key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
310
- end
311
-
312
- def flat_initial_config(key = nil)
313
- fc = initial_config.flatten_with_type
314
- key.nil? ? fc : fc.select{ |c| c[:name] == key.to_sym }.first.try(:value)
315
- end
316
-
317
- # Returns a hash built from all persistent config values for the current widget, following the double underscore
318
- # naming convention. E.g., if we have the following persistent config pairs:
319
- # enabled => true
320
- # layout__width => 100
321
- # layout__header__height => 20
322
- #
323
- # this method will return the following hash:
324
- # {:enabled => true, :layout => {:width => 100, :header => {:height => 20}}}
325
- def persistent_config_hash
326
- return {} if !initial_config[:persistent_config] || Netzke::Base.persistent_config_manager_class.nil?
327
74
 
328
- prefs = NetzkePreference.find_all_for_widget(persistence_key.to_s)
329
- res = {}
330
- prefs.each do |p|
331
- hsh_levels = p.name.split("__").map(&:to_sym)
332
- tmp_res = {} # it decends into itself, building itself
333
- anchor = {} # it will keep the tail of tmp_res
334
- hsh_levels.each do |level_prefix|
335
- tmp_res[level_prefix] ||= level_prefix == hsh_levels.last ? p.normalized_value : {}
336
- anchor = tmp_res[level_prefix] if level_prefix == hsh_levels.first
337
- tmp_res = tmp_res[level_prefix]
338
- end
339
- # Now 'anchor' is a hash that represents the path to the single value,
340
- # for example: {:ext_config => {:title => 100}} (which corresponds to ext_config__title)
341
- # So we need to recursively merge it into the final result
342
- res.deep_merge!(hsh_levels.first => anchor)
343
- end
344
- res.deep_convert_keys{ |k| k.to_sym } # recursively symbolize the keys
345
- end
346
- memoize :persistent_config_hash
347
-
348
- # Helper to access config[:ext_config]
349
- def ext_config
350
- config[:ext_config] || {}
351
- end
352
-
353
- # Like normal config, but stored in session
354
- def weak_session_config
355
- widget_session[:weak_session_config] ||= {}
356
- end
357
-
358
- def strong_session_config
359
- widget_session[:strong_session_config] ||= {}
75
+ # initialize @components and @items
76
+ normalize_components_in_items
77
+ # auto_collect_actions_from_config_and_js_properties
78
+ end
79
+
80
+ # Short component class name, e.g.:
81
+ # Netzke::Module::SomeComponent => Module::SomeComponent
82
+ def self.short_component_class_name
83
+ self.name.sub(/^Netzke::/, "")
360
84
  end
361
85
 
362
- # configuration of all children will get deep_merge'd with strong_children_config
363
- # def strong_children_config= (c)
364
- # @strong_children_config = c
365
- # end
366
-
367
- # This config will be picked up by all the descendants
368
- def strong_children_config
369
- @strong_children_config ||= parent.nil? ? {} : parent.strong_children_config
86
+ # Component class, given its name
87
+ def self.constantize_class_name(class_name)
88
+ "#{class_name}".constantize rescue "Netzke::#{class_name}".constantize
370
89
  end
371
90
 
372
- # configuration of all children will get reverse_deep_merge'd with weak_children_config
373
- # def weak_children_config= (c)
374
- # @weak_children_config = c
375
- # end
376
-
377
- def weak_children_config
378
- @weak_children_config ||= {}
379
- end
380
-
381
- def widget_session
382
- session[global_id] ||= {}
91
+ def constantize_class_name(class_name)
92
+ self.class.constantize_class_name(class_name)
383
93
  end
384
94
 
385
- # Rails' logger
386
- def logger
387
- Rails.logger
95
+ # Instance of component by config
96
+ def self.instance_by_config(config)
97
+ constantize_class_name(config[:class_name]).new(config)
388
98
  end
389
-
390
- def dependency_classes
391
- res = []
392
- non_late_aggregatees.keys.each do |aggr|
393
- res += aggregatee_instance(aggr).dependency_classes
99
+
100
+ def logger
101
+ if defined?(Rails)
102
+ Rails.logger
103
+ else
104
+ require 'logger'
105
+ Logger.new(STDOUT)
394
106
  end
395
- res << short_widget_class_name
396
- res.uniq
397
107
  end
398
-
108
+
399
109
  # 'Netzke::Grid' => 'Grid'
400
- def short_widget_class_name
401
- self.class.short_widget_class_name
402
- end
403
-
404
- ## Dependencies
405
- def dependencies
406
- @dependencies ||= begin
407
- non_late_aggregatees_widget_classes = non_late_aggregatees.values.map{|v| v[:class_name]}
408
- (initial_dependencies + non_late_aggregatees_widget_classes << self.class.short_widget_class_name).uniq
409
- end
410
- end
411
-
412
- # override this method if you need some extra dependencies, which are not the aggregatees
413
- def initial_dependencies
414
- []
415
- end
416
-
417
- ### Aggregation
418
- def initial_aggregatees
419
- {}
420
- end
421
-
422
- def aggregatees
423
- @aggregatees ||= initial_aggregatees.merge(initial_late_aggregatees.each_pair{|k,v| v.merge!(:late_aggregation => true)})
424
- end
425
-
426
- def non_late_aggregatees
427
- aggregatees.reject{|k,v| v[:late_aggregation]}
428
- end
429
-
430
- def add_aggregatee(aggr)
431
- aggregatees.merge!(aggr)
110
+ def short_component_class_name
111
+ self.class.short_component_class_name
432
112
  end
433
-
434
- def remove_aggregatee(aggr)
435
- if config[:persistent_config]
436
- persistent_config_manager_class.delete_all_for_widget("#{global_id}__#{aggr}")
437
- end
438
- aggregatees[aggr] = nil
439
- end
440
-
441
- # The difference between aggregatees and late aggregatees is the following: the former gets instantiated together with its aggregator and is normally *instantly* visible as a part of it (for example, the widget in the initially expanded panel in an Accordion). A late aggregatee doesn't get instantiated along with its aggregator. Until it gets requested from the server, it doesn't take any part in its aggregator's life. An example of late aggregatee could be a widget that is loaded dynamically into a previously collapsed panel of an Accordion, or a preferences window (late aggregatee) for a widget (aggregator) that only gets shown when user wants to edit widget's preferences.
442
- def initial_late_aggregatees
443
- {}
444
- end
445
-
446
- def add_late_aggregatee(aggr)
447
- aggregatees.merge!(aggr.merge(:late_aggregation => true))
448
- end
449
-
450
- # recursively instantiates an aggregatee based on its "path": e.g. if we have an aggregatee :aggr1 which in its turn has an aggregatee :aggr10, the path to the latter would be "aggr1__aggr10"
451
- # TODO: introduce memoization
452
- def aggregatee_instance(name, strong_config = {})
453
- aggregator = self
454
- name.to_s.split('__').each do |aggr|
455
- aggr = aggr.to_sym
456
- aggregatee_config = aggregator.aggregatees[aggr]
457
- raise ArgumentError, "No aggregatee '#{aggr}' defined for widget '#{aggregator.global_id}'" if aggregatee_config.nil?
458
- ::ActiveSupport::Deprecation.warn("widget_class_name option is deprecated. Use class_name instead", caller) if aggregatee_config[:widget_class_name]
459
- short_widget_class_name = aggregatee_config[:class_name] || aggregatee_config[:widget_class_name]
460
- raise ArgumentError, "No class_name specified for aggregatee #{aggr} of #{aggregator.global_id}" if short_widget_class_name.nil?
461
- widget_class = "Netzke::#{short_widget_class_name}".constantize
462
-
463
- conf = weak_children_config.
464
- deep_merge(aggregatee_config).
465
- deep_merge(strong_config). # we may want to reconfigure the aggregatee at the moment of instantiation
466
- merge(:name => aggr)
467
-
468
- aggregator = widget_class.new(conf, aggregator) # params: config, parent
469
- # aggregator.weak_children_config = weak_children_config
470
- # aggregator.strong_children_config = strong_children_config
471
- end
472
- aggregator
473
- end
474
-
475
- def full_class_name(short_name)
476
- "Netzke::#{short_name}"
477
- end
478
-
113
+
479
114
  def flash(flash_hash)
480
115
  level = flash_hash.keys.first
481
116
  raise "Unknown message level for flash" unless %(notice warning error).include?(level.to_s)
482
117
  @flash << {:level => level, :msg => flash_hash[level]}
483
118
  end
484
119
 
485
- def widget_action(action_name)
486
- "#{@global_id}__#{action_name}"
487
- end
488
-
489
- # called when the method_missing tries to processes a non-existing aggregatee
490
- def aggregatee_missing(aggr)
491
- flash :error => "Unknown aggregatee #{aggr} for widget #{name}"
492
- {:feedback => @flash}.to_nifty_json
493
- end
494
-
495
- def tools
496
- persistent_config[:tools] ||= config[:tools] || []
497
- end
498
-
499
- def menu
500
- persistent_config[:menu] ||= config[:menu] == false ? nil : config[:menu]
120
+ def self.read_clean_inheritable_hash(attr_name)
121
+ res = read_inheritable_attribute(attr_name) || {}
122
+ # We don't want here any values from the superclass (which is the consequence of using inheritable attributes).
123
+ res == self.superclass.read_inheritable_attribute(attr_name) ? {} : res
501
124
  end
502
125
 
503
- # some convenience for instances
504
- def persistent_config_manager_class
505
- self.class.persistent_config_manager_class
506
- end
507
-
508
126
  # override this method to do stuff at the moment of loading by some parent
509
127
  def before_load
510
- widget_session.clear
128
+ # component_session.clear - we don't do it anymore
511
129
  end
512
130
 
513
- # Returns global id of a widget in the hierarchy, based on passed reference that follows
514
- # the double-underscore notation. Referring to "parent" is allowed. If going to far up the hierarchy will
515
- # result in <tt>nil</tt>, while referring to a non-existent aggregatee will simply provide an erroneous ID.
516
- # Example:
517
- # <tt>parent__parent__child__subchild</tt> will traverse the hierarchy 2 levels up, then going down to "child",
518
- # and further to "subchild". If such a widget exists in the hierarchy, its global id will be returned, otherwise
519
- # <tt>nil</tt> will be returned.
520
- def global_id_by_reference(ref)
521
- ref = ref.to_s
522
- return parent && parent.global_id if ref == "parent"
523
- substr = ref.sub(/^parent__/, "")
524
- if substr == ref # there's no "parent__" in the beginning
525
- return global_id + "__" + ref
526
- else
527
- return parent.global_id_by_reference(substr)
528
- end
529
- end
530
131
 
531
- # API: provides what is necessary for the browser to render a widget.
532
- # <tt>params</tt> should contain:
533
- # * <tt>:cache</tt> - an array of widget classes cached at the browser
534
- # * <tt>:id</tt> - reference to the aggregatee
535
- # * <tt>:container</tt> - Ext id of the container where in which the aggregatee will be rendered
536
- def load_aggregatee_with_cache(params)
537
- cache = params[:cache].gsub(".", "::").split(",") # array of cached class names (in Ruby)
538
- relative_widget_id = params.delete(:id).underscore.to_sym
539
- widget = aggregatees[relative_widget_id] && aggregatee_instance(relative_widget_id)
540
-
541
- if widget
542
- # inform the widget that it's being loaded
543
- widget.before_load
544
-
545
- [{
546
- :js => widget.js_missing_code(cache),
547
- :css => widget.css_missing_code(cache)
548
- }, {
549
- :render_widget_in_container => { # TODO: rename it
550
- :container => params[:container],
551
- :config => widget.js_config
552
- }
553
- }, {
554
- :widget_loaded => {
555
- :id => relative_widget_id
556
- }
557
- }]
558
- else
559
- {:feedback => "Couldn't load aggregatee '#{relative_widget_id}'"}
560
- end
561
- end
562
-
563
- # Method dispatcher - instantiates an aggregatee and calls the method on it
564
- # E.g.:
565
- # users__center__get_data
566
- # instantiates aggregatee "users", and calls "center__get_data" on it
567
- # books__move_column
568
- # instantiates aggregatee "books", and calls "api_move_column" on it
569
- def method_missing(method_name, params = {})
570
- widget, *action = method_name.to_s.split('__')
571
- widget = widget.to_sym
572
- action = !action.empty? && action.join("__").to_sym
573
-
574
- if action
575
- if aggregatees[widget]
576
- # only actions starting with "api_" are accessible
577
- api_action = action.to_s.index('__') ? action : "api_#{action}"
578
- aggregatee_instance(widget).send(api_action, params)
579
- else
580
- aggregatee_missing(widget)
581
- end
132
+ # Dependencies
133
+ def self.class_ancestors
134
+ if self == Netzke::Base
135
+ []
582
136
  else
583
- super
137
+ superclass.class_ancestors + [self]
584
138
  end
585
139
  end
586
140
 
587
- # Register the configuration for the widget in the session, and also remember that the code for it has been rendered
588
- def self.reg_widget(config)
589
- session[:netzke_widgets] ||= {}
590
- session[:netzke_widgets][config[:name]] = config
591
- end
592
-
593
141
  end
594
142
  end