netzke-core 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ module Netzke
2
+ # == BaseJs
3
+ # *TODO: outdated*
4
+ #
5
+ # Module which provides JS-class generation functionality for the widgets ("client-side"). The generated code
6
+ # is evaluated once per widget class, and the results are cached in the browser. Included into Netzke::Base class.
7
+ #
8
+ # == Widget javascript code
9
+ # Here's a brief explanation on how a javascript class for a widget gets built.
10
+ # Widget gets defined as a constructor (a function) by +js_class+ class method (see "Inside widget's contstructor").
11
+ # +Ext.extend+ provides inheritance from an Ext class specified in +js_base_class+ class method.
12
+ #
13
+ # == Inside widget's constructor
14
+ # * Widget's constructor gets called with a parameter that is a configuration object provided by +js_config+ instance method. This configuration is specific for the instance of the widget, and, for example, contains this widget's unique id. As another example, by means of this configuration object, a grid receives the configuration array for its columns, a form - for its fields, etc. With other words, everything that may change from instance to instance of the same widget's class, goes in here.
15
+ # * Widget executes its specific initialization code which is provided by +js_before_consttructor+ class method.
16
+ # For example, a grid may define its column model, a form - its fields, a tab panel - its tabs ("items").
17
+ # * Widget calls the constructor of the inherited class (see +js_class+ class method) with a parameter that is a merge of
18
+ # 1) configuration parameter passed to the widget's constructor.
19
+ module BaseJs
20
+ def self.included(base)
21
+ base.extend ClassMethods
22
+ end
23
+
24
+ #
25
+ # Instance methods
26
+ #
27
+
28
+ # Config that is used for instantiating the widget in javascript
29
+ def js_config
30
+ res = {}
31
+
32
+ # Unique id of the widget
33
+ res.merge!(:id => id_name)
34
+
35
+ # Recursively include configs of all non-late aggregatees, so that the widget can instantiate them
36
+ # in javascript immediately.
37
+ non_late_aggregatees.each_pair do |aggr_name, aggr_config|
38
+ aggr_instance = aggregatee_instance(aggr_name.to_sym)
39
+ aggr_instance.before_load
40
+ res["#{aggr_name}_config".to_sym] = aggr_instance.js_config
41
+ end
42
+
43
+ # Api (besides the default "load_aggregatee_with_cache" - JavaScript side already knows about it)
44
+ api_points = self.class.api_points.reject{ |p| p == :load_aggregatee_with_cache }
45
+ res.merge!(:api => api_points) unless api_points.empty?
46
+
47
+ # Widget class name. Needed for dynamic instantiation in javascript.
48
+ res.merge!(:widget_class_name => short_widget_class_name)
49
+
50
+ # Actions, toolbars and menus
51
+ # tools && res.merge!(:tools => tools)
52
+ actions && res.merge!(:actions => actions)
53
+ menu && res.merge!(:menu => menu)
54
+
55
+ # Merge with all config options passed as hash to config[:ext_config]
56
+ res.merge!(ext_config)
57
+
58
+ res
59
+ end
60
+
61
+ # All the JS-code required by this instance of the widget to be instantiated in the browser.
62
+ # It includes the JS-class for the widget itself, as well as JS-classes for all widgets' (non-late) aggregatees.
63
+ def js_missing_code(cached_dependencies = [])
64
+ code = dependency_classes.inject("") do |r,k|
65
+ cached_dependencies.include?(k) ? r : r + "Netzke::#{k}".constantize.js_code(cached_dependencies).strip_js_comments
66
+ end
67
+ code.blank? ? nil : code
68
+ end
69
+
70
+ def css_missing_code(cached_dependencies = [])
71
+ code = dependency_classes.inject("") do |r,k|
72
+ cached_dependencies.include?(k) ? r : r + "Netzke::#{k}".constantize.css_code(cached_dependencies)
73
+ end
74
+ code.blank? ? nil : code
75
+ end
76
+
77
+ #
78
+ # The following methods are used when a widget is generated stand-alone (as a part of a HTML page)
79
+ #
80
+
81
+ # instantiating
82
+ def js_widget_instance
83
+ %Q{var #{name.jsonify} = new Ext.netzke.cache.#{short_widget_class_name}(#{js_config.to_nifty_json});}
84
+ end
85
+
86
+ # rendering
87
+ def js_widget_render
88
+ %Q{#{name.jsonify}.render("#{name.to_s.split('_').join('-')}");}
89
+ end
90
+
91
+ # container for rendering
92
+ def js_widget_html
93
+ %Q{<div id="#{name.to_s.split('_').join('-')}"></div>}
94
+ end
95
+
96
+ #
97
+ #
98
+ #
99
+
100
+ # Widget's actions, tools and menus that are loaded at the moment of instantiation
101
+ def actions; nil; end
102
+ def menu; nil; end
103
+ # def tools; nil; end
104
+
105
+ # Little helpers
106
+ def this; "this".l; end
107
+ def null; "null".l; end
108
+
109
+ # Methods used to create the javascript class (only once per widget class).
110
+ # The generated code gets cached at the browser, and the widget intstances (at the browser side)
111
+ # get instantiated from it.
112
+ # All these methods can be overwritten in case you want to extend the functionality of some pre-built widget
113
+ # instead of using it as is (using both would cause JS-code duplication)
114
+ module ClassMethods
115
+ # the JS (Ext) class that we inherit from on JS-level
116
+ def js_base_class
117
+ "Ext.Panel"
118
+ end
119
+
120
+ # functions and properties that will be used to extend the functionality of (Ext) JS-class specified in js_base_class
121
+ def js_extend_properties
122
+ {}
123
+ end
124
+
125
+ # widget's menus
126
+ def js_menus; []; end
127
+
128
+ # items
129
+ # def js_items; null; end
130
+
131
+ # are we using JS inheritance? for now, if js_base_class is a Netzke class - yes
132
+ def js_inheritance?
133
+ superclass != Netzke::Base
134
+ end
135
+
136
+ # Declaration of widget's class (stored in the cache storage (Ext.netzke.cache) at the client side
137
+ # to be reused at the moment of widget instantiation)
138
+ def js_class
139
+ if js_inheritance?
140
+ # In case of using javascript inheritance, little needs to be done
141
+ <<-END_OF_JAVASCRIPT
142
+ // Create the class
143
+ Ext.netzke.cache.#{short_widget_class_name} = function(config){
144
+ Ext.netzke.cache.#{short_widget_class_name}.superclass.constructor.call(this, config);
145
+ };
146
+ // Extend it with the class that we inherit from, and mix in js_extend_properties
147
+ Ext.extend(Ext.netzke.cache.#{short_widget_class_name}, Ext.netzke.cache.#{superclass.short_widget_class_name}, Ext.applyIf(#{js_extend_properties.to_nifty_json}, Ext.widgetMixIn));
148
+ END_OF_JAVASCRIPT
149
+ else
150
+ js_add_menus = "this.addMenus(#{js_menus.to_nifty_json});" unless js_menus.empty?
151
+ <<-END_OF_JAVASCRIPT
152
+ // Constructor
153
+ Ext.netzke.cache.#{short_widget_class_name} = function(config){
154
+ // Do all the initializations that every Netzke widget should do: create methods for API-points,
155
+ // process actions, tools, toolbars
156
+ this.commonBeforeConstructor(config);
157
+
158
+ // Call the constructor of the inherited class
159
+ Ext.netzke.cache.#{short_widget_class_name}.superclass.constructor.call(this, config);
160
+
161
+ // What every widget should do after calling the constructor of the inherited class, like
162
+ // setting extra events
163
+ this.commonAfterConstructor(config);
164
+ };
165
+ Ext.extend(Ext.netzke.cache.#{short_widget_class_name}, #{js_base_class}, Ext.applyIf(#{js_extend_properties.to_nifty_json}, Ext.widgetMixIn));
166
+ END_OF_JAVASCRIPT
167
+ end
168
+ end
169
+
170
+ #
171
+ # Extra JavaScript
172
+ #
173
+
174
+ # Override this method. Must return an array of paths to javascript files that we depend on.
175
+ # This javascript code will be loaded along with the widget's class, and before it.
176
+ def include_js
177
+ []
178
+ end
179
+
180
+ # Returns all extra JavaScript-code (as string) required by this widget's class
181
+ def js_included
182
+ res = ""
183
+
184
+ include_js.each do |path|
185
+ f = File.new(path)
186
+ res << f.read << "\n"
187
+ end
188
+
189
+ res
190
+ end
191
+
192
+ # All JavaScript code needed for this class, including one from the ancestor widget
193
+ def js_code(cached_dependencies = [])
194
+ res = ""
195
+
196
+ # include the base-class javascript if doing JS inheritance
197
+ res << superclass.js_code << "\n" if js_inheritance? && !cached_dependencies.include?(superclass.short_widget_class_name)
198
+
199
+ # include static javascripts
200
+ res << js_included << "\n"
201
+
202
+ # our own JS class definition
203
+ res << js_class
204
+ res
205
+ end
206
+
207
+ #
208
+ # Extra CSS
209
+ #
210
+
211
+ # Override this method. Must return an array of paths to css files that we depend on.
212
+ def include_css
213
+ []
214
+ end
215
+
216
+ # Returns all extra CSS code (as string) required by this widget's class
217
+ def css_included
218
+ res = ""
219
+
220
+ include_css.each do |path|
221
+ f = File.new(path)
222
+ res << f.read << "\n"
223
+ end
224
+
225
+ res
226
+ end
227
+
228
+ # All CSS code needed for this class including the one from the ancestor widget
229
+ def css_code(cached_dependencies = [])
230
+ res = ""
231
+
232
+ # include the base-class javascript if doing JS inheritance
233
+ res << superclass.css_code << "\n" if js_inheritance? && !cached_dependencies.include?(superclass.short_widget_class_name)
234
+
235
+ res << css_included << "\n"
236
+
237
+ res
238
+ end
239
+
240
+
241
+ # Little helper
242
+ def this; "this".l; end
243
+
244
+ # Little helper
245
+ def null; "null".l; end
246
+
247
+ end
248
+ end
249
+ end
@@ -7,9 +7,9 @@ module Netzke
7
7
 
8
8
  def set_session_data
9
9
  Netzke::Base.session = session
10
- session[:user] = defined?(current_user) ? current_user : nil
10
+ session[:netzke_user_id] = defined?(current_user) ? current_user.try(:id) : nil
11
11
 
12
- Netzke::Base.user = session[:user] # for backward compatibility (TODO: eliminate the need for this)
12
+ Netzke::Base.user = current_user # for backward compatibility (TODO: eliminate the need for this)
13
13
 
14
14
  # set netzke_just_logged_in and netzke_just_logged_out states (may be used by Netzke widgets)
15
15
  if session[:_netzke_next_request_is_first_after_login]
@@ -35,16 +35,19 @@ module Netzke
35
35
  widget = widget.to_sym
36
36
  action = !action.empty? && action.join("__").to_sym
37
37
 
38
- # only widget's actions starting with "interface_" are accessible from outside (security)
38
+ # only widget's actions starting with "api_" are accessible from outside (security)
39
39
  if action
40
- interface_action = action.to_s.index('__') ? action : "interface_#{action}"
40
+ api_action = action.to_s.index('__') ? action : "api_#{action}"
41
41
 
42
42
  # widget module
43
43
  widget_class = "Netzke::#{self.class.widget_config_storage[widget][:widget_class_name]}".constantize
44
44
 
45
45
  # instantiate the server part of the widget
46
- widget_instance = widget_class.new(self.class.widget_config_storage[widget].merge(:controller => self)) # OPTIMIZE: top-level widgets have access to the controller - can we avoid that?
47
- render :text => widget_instance.send(interface_action, params)
46
+ widget_instance = widget_class.new(self.class.widget_config_storage[widget])
47
+ # (OLD VERSION)
48
+ # widget_instance = widget_class.new(self.class.widget_config_storage[widget].merge(:controller => self)) # OPTIMIZE: top-level widgets have access to the controller - can we avoid that?
49
+
50
+ render :text => widget_instance.send(api_action, params)
48
51
  end
49
52
  end
50
53
  end
@@ -54,6 +57,7 @@ module Netzke
54
57
  # widget_config_storage for all widgets
55
58
  def widget_config_storage
56
59
  @@widget_config_storage ||= {}
60
+ @@widget_config_storage[self.name] ||= {} # specific for controller
57
61
  end
58
62
 
59
63
  #
@@ -69,53 +73,38 @@ module Netzke
69
73
 
70
74
  # provide widget helpers
71
75
  ActionView::Base.module_eval <<-END_EVAL, __FILE__, __LINE__
76
+ def #{name}_server_instance(config = {})
77
+ default_config = controller.class.widget_config_storage[:#{name}]
78
+ if config.empty?
79
+ # only cache when the config is empty (which means that config specified in controller is used)
80
+ @widget_instance_cache ||= {}
81
+ @widget_instance_cache[:#{name}] ||= Netzke::Base.instance_by_config(default_config)
82
+ else
83
+ # if helper is called with parameters - always return a fresh instance of widget, no caching
84
+ Netzke::Base.instance_by_config(default_config.deep_merge(config))
85
+ end
86
+ end
87
+
72
88
  def #{name}_widget_instance(config = {})
73
- # get the global config from the controller's singleton class
74
- global_config = controller.class.widget_config_storage[:#{name}]
75
-
76
- # when instantiating a client side instance, the configuration may be overwritten
77
- # (but the server side will know nothing about it!)
78
- local_config = global_config.merge(config)
79
-
80
- # instantiate it
81
- widget_instance = Netzke::#{config[:widget_class_name]}.new(local_config)
82
-
83
- # return javascript code for instantiating on the javascript level
84
- widget_instance.js_widget_instance
89
+ #{name}_server_instance(config).js_widget_instance
85
90
  end
86
91
 
87
- def #{name}_class_definition_old
88
- result = ""
89
- config = controller.class.widget_config_storage[:#{name}]
90
- @generated_widget_classes ||= []
91
- # do not duplicate javascript code on the same page
92
- unless @generated_widget_classes.include?("#{config[:widget_class_name]}")
93
- @generated_widget_classes << "#{config[:widget_class_name]}"
94
- result = Netzke::#{config[:widget_class_name]}.js_class_code
95
- end
96
- result
97
- end
98
-
99
92
  def #{name}_class_definition
100
93
  @generated_widget_classes ||= []
101
- config = controller.class.widget_config_storage[:#{name}]
102
- widget_instance = Netzke::#{config[:widget_class_name]}.new(config)
103
- res = widget_instance.js_missing_code(@generated_widget_classes)
104
- @generated_widget_classes += widget_instance.dependencies
94
+ res = #{name}_server_instance.js_missing_code(@generated_widget_classes)
95
+
96
+ # prevent duplication of javascript when multiple homogeneous widgets are on the same page
97
+ @generated_widget_classes += #{name}_server_instance.dependencies
105
98
  @generated_widget_classes.uniq!
106
99
  res
107
100
  end
108
101
 
109
102
  def #{name}_widget_html
110
- config = controller.class.widget_config_storage[:#{name}]
111
- widget_instance = Netzke::Base.instance_by_config(config)
112
- widget_instance.js_widget_html
103
+ #{name}_server_instance.js_widget_html
113
104
  end
114
105
 
115
106
  def #{name}_widget_render
116
- config = controller.class.widget_config_storage[:#{name}]
117
- widget_instance = Netzke::Base.instance_by_config(config)
118
- widget_instance.js_widget_render
107
+ #{name}_server_instance.js_widget_render
119
108
  end
120
109
 
121
110
  END_EVAL
@@ -9,17 +9,28 @@ class Hash
9
9
  h
10
10
  end : self
11
11
  end
12
+
13
+ def jsonify
14
+ self.inject({}) do |h,(k,v)|
15
+ new_key = k.instance_of?(String) || k.instance_of?(Symbol) ? k.jsonify : k
16
+ new_value = v.instance_of?(Array) || v.instance_of?(Hash) ? v.jsonify : v
17
+ h.merge(new_key => new_value)
18
+ end
19
+ end
12
20
 
13
21
  # First camelizes the keys, then convert the whole hash to JSON
14
- def to_js
15
- self.recursive_delete_if_nil.convert_keys{|k| k.camelize(:lower)}.to_json
16
- end
17
-
18
- # Converts values to strings
19
- def stringify_values!
20
- self.each_pair{|k,v| self[k] = v.to_s if v.is_a?(Symbol)}
22
+ def to_nifty_json
23
+ self.recursive_delete_if_nil.jsonify.to_json
21
24
  end
22
25
 
26
+ # Converts values of a Hash in such a way that they can be easily stored in the database: hashes and arrays are jsonified, symbols - stringified
27
+ def deebeefy_values
28
+ inject({}) do |options, (k, v)|
29
+ options[k] = v.is_a?(Symbol) ? v.to_s : (v.is_a?(Hash) || v.is_a?(Array)) ? v.to_json : v
30
+ options
31
+ end
32
+ end
33
+
23
34
  # We don't need to pass null values in JSON, they are null by simply being absent
24
35
  def recursive_delete_if_nil
25
36
  self.inject({}) do |h,(k,v)|
@@ -45,9 +56,13 @@ class Hash
45
56
  end
46
57
 
47
58
  class Array
59
+ def jsonify
60
+ self.map{ |el| el.instance_of?(Array) || el.instance_of?(Hash) ? el.jsonify : el }
61
+ end
62
+
48
63
  # Camelizes the keys of hashes and converts them to JSON
49
- def to_js
50
- self.recursive_delete_if_nil.map{|el| el.is_a?(Hash) ? el.convert_keys{|k| k.camelize(:lower)} : el}.to_json
64
+ def to_nifty_json
65
+ self.recursive_delete_if_nil.jsonify.to_json
51
66
  end
52
67
 
53
68
  # Applies convert_keys to each element which responds to convert_keys
@@ -62,19 +77,22 @@ class Array
62
77
  end
63
78
  end
64
79
 
65
- class String
66
- # Converts self to "literal JSON"-string - one that doesn't get quotes appended when being sent "to_json" method
67
- def l
68
- def self.to_json(options={})
69
- self
70
- end
80
+ class LiteralString < String
81
+ def to_json(*args)
71
82
  self
72
83
  end
73
-
74
- def to_js
84
+ end
85
+
86
+ class String
87
+ def jsonify
75
88
  self.camelize(:lower)
76
89
  end
77
90
 
91
+ # Converts self to "literal JSON"-string - one that doesn't get quotes appended when being sent "to_json" method
92
+ def l
93
+ LiteralString.new(self)
94
+ end
95
+
78
96
  # removes JS-comments (both single- and multi-line) from the string
79
97
  def strip_js_comments
80
98
  regexp = /\/\/.*$|(?m:\/\*.*?\*\/)/
@@ -92,9 +110,13 @@ class String
92
110
  end
93
111
 
94
112
  class Symbol
95
- def to_js
113
+ def jsonify
96
114
  self.to_s.camelize(:lower).to_sym
97
115
  end
116
+
117
+ def l
118
+ LiteralString.new(self.to_s)
119
+ end
98
120
  end
99
121
 
100
122
  module ActiveSupport