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.
- data/.autotest +1 -0
- data/.gitignore +4 -0
- data/CHANGELOG +21 -0
- data/Manifest +9 -11
- data/README.rdoc +12 -0
- data/Rakefile +17 -13
- data/TODO +2 -1
- data/VERSION +1 -0
- data/generators/netzke_core/templates/create_netzke_preferences.rb +1 -1
- data/javascripts/core.js +271 -113
- data/lib/app/controllers/netzke_controller.rb +66 -9
- data/lib/app/models/netzke_preference.rb +39 -32
- data/lib/netzke-core.rb +0 -3
- data/lib/netzke/base.rb +318 -140
- data/lib/netzke/base_js.rb +249 -0
- data/lib/netzke/controller_extensions.rb +29 -40
- data/lib/netzke/core_ext.rb +40 -18
- data/lib/netzke/feedback_ghost.rb +2 -2
- data/netzke-core.gemspec +90 -11
- data/test/unit/core_ext_test.rb +28 -7
- data/test/unit/netzke_core_test.rb +57 -29
- data/test/unit/netzke_preference_test.rb +7 -7
- metadata +35 -38
- data/README.mdown +0 -15
- data/lib/netzke/base_extras/interface.rb +0 -20
- data/lib/netzke/base_extras/js_builder.rb +0 -271
- data/lib/vendor/facets/hash/recursive_merge.rb +0 -28
@@ -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[:
|
10
|
+
session[:netzke_user_id] = defined?(current_user) ? current_user.try(:id) : nil
|
11
11
|
|
12
|
-
Netzke::Base.user =
|
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 "
|
38
|
+
# only widget's actions starting with "api_" are accessible from outside (security)
|
39
39
|
if action
|
40
|
-
|
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]
|
47
|
-
|
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
|
-
#
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
@generated_widget_classes +=
|
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
|
-
|
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
|
-
|
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
|
data/lib/netzke/core_ext.rb
CHANGED
@@ -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
|
15
|
-
self.recursive_delete_if_nil.
|
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
|
50
|
-
self.recursive_delete_if_nil.
|
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
|
-
|
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
|
-
|
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
|
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
|