skozlov-netzke-core 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/LICENSE +20 -0
- data/Rakefile +15 -0
- data/generators/netzke_core/USAGE +8 -0
- data/generators/netzke_core/netzke_core_generator.rb +13 -0
- data/generators/netzke_core/templates/create_netzke_layouts.rb +14 -0
- data/generators/netzke_core/templates/create_netzke_preferences.rb +18 -0
- data/generators/netzke_core/templates/netzke.html.erb +10 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/javascripts/core.js +126 -0
- data/lib/app/controllers/netzke_controller.rb +16 -0
- data/lib/app/models/netzke_layout.rb +75 -0
- data/lib/app/models/netzke_preference.rb +66 -0
- data/lib/netzke/base.rb +198 -0
- data/lib/netzke/controller_extensions.rb +87 -0
- data/lib/netzke/core_ext.rb +77 -0
- data/lib/netzke/js_class_builder.rb +125 -0
- data/lib/netzke-core.rb +22 -0
- data/lib/vendor/facets/hash/recursive_merge.rb +28 -0
- data/netzke-core.gemspec +32 -0
- data/tasks/netzke_core_tasks.rake +4 -0
- data/test/app_root/app/controllers/application.rb +2 -0
- data/test/app_root/config/boot.rb +114 -0
- data/test/app_root/config/database.yml +21 -0
- data/test/app_root/config/environment.rb +13 -0
- data/test/app_root/config/environments/in_memory.rb +0 -0
- data/test/app_root/config/environments/mysql.rb +0 -0
- data/test/app_root/config/environments/postgresql.rb +0 -0
- data/test/app_root/config/environments/sqlite.rb +0 -0
- data/test/app_root/config/environments/sqlite3.rb +0 -0
- data/test/app_root/config/routes.rb +4 -0
- data/test/app_root/script/console +6 -0
- data/test/core_ext_test.rb +35 -0
- data/test/netzke_core_test.rb +136 -0
- data/test/test_helper.rb +20 -0
- data/uninstall.rb +1 -0
- metadata +109 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Sergei Kozlov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'echoe'
|
2
|
+
|
3
|
+
Echoe.new("netzke-core") do |p|
|
4
|
+
p.author = "Sergei Kozlov"
|
5
|
+
p.email = "sergei@writelesscode.com"
|
6
|
+
p.summary = "Build ExtJS/Rails widgets with minimum effort"
|
7
|
+
p.url = "http://writelesscode.com"
|
8
|
+
# p.runtime_dependencies = ["searchlogic >=1.6.2"]
|
9
|
+
p.development_dependencies = []
|
10
|
+
p.test_pattern = 'test/**/*_test.rb'
|
11
|
+
p.retain_gemspec = true
|
12
|
+
|
13
|
+
# fixing the problem with lib/*-* files being removed while doing manifest
|
14
|
+
p.clean_pattern = ["pkg", "doc", 'build/*', '**/coverage', '**/*.o', '**/*.so', '**/*.a', '**/*.log', "{ext,lib}/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/Makefile", "{ext,lib}/**/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/**/Makefile", "pkg", "*.gem", ".config"]
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# class NetzkeCoreGenerator < Rails::Generator::NamedBase
|
2
|
+
class NetzkeCoreGenerator < Rails::Generator::Base
|
3
|
+
def manifest
|
4
|
+
record do |m|
|
5
|
+
m.directory "public/javascripts/netzke"
|
6
|
+
# m.file 'netzke.js', "public/javascripts/netzke/netzke.js"
|
7
|
+
m.file 'netzke.html.erb', "app/views/layouts/netzke.html.erb"
|
8
|
+
m.migration_template 'create_netzke_preferences.rb', "db/migrate", {:migration_file_name => "create_netzke_preferences"}
|
9
|
+
# FIXME: how do we avoid getting the same migration IDs?
|
10
|
+
m.migration_template 'create_netzke_layouts.rb', "db/migrate", {:migration_file_name => "create_netzke_layouts"}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateNetzkeLayouts < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :netzke_layouts do |t|
|
4
|
+
t.string :widget_name
|
5
|
+
t.string :items_class
|
6
|
+
t.integer :user_id
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
drop_table :netzke_layouts
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateNetzkePreferences < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :netzke_preferences do |t|
|
4
|
+
t.string :name
|
5
|
+
t.string :pref_type
|
6
|
+
t.string :value
|
7
|
+
t.integer :user_id
|
8
|
+
t.integer :role_id
|
9
|
+
t.string :custom_field
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :netzke_preferences
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<head>
|
2
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
3
|
+
<title>netzke widget</title>
|
4
|
+
<%= javascript_include_tag("/extjs/adapter/ext/ext-base.js", "/extjs/ext-all-debug.js") %>
|
5
|
+
<%= javascript_include_tag("netzke/netzke.js") %>
|
6
|
+
<%= stylesheet_link_tag("/extjs/resources/css/ext-all.css") %>
|
7
|
+
</head>
|
8
|
+
<body id="">
|
9
|
+
<%= yield %>
|
10
|
+
</body>
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'netzke-core'
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/javascripts/core.js
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
Ext.BLANK_IMAGE_URL = "/extjs/resources/images/default/s.gif";
|
2
|
+
Ext.componentCache = {};
|
3
|
+
|
4
|
+
Ext.namespace('Ext.netzke');
|
5
|
+
|
6
|
+
// helper method to do multiple Ext.apply's
|
7
|
+
Ext.chainApply = function(objectArray){
|
8
|
+
var res = {};
|
9
|
+
Ext.each(objectArray, function(obj){Ext.apply(res, obj)});
|
10
|
+
return res;
|
11
|
+
}
|
12
|
+
|
13
|
+
// implementation of totalProperty, successProperty and root configuration options for ArrayReader
|
14
|
+
Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
|
15
|
+
readRecords : function(o){
|
16
|
+
var sid = this.meta ? this.meta.id : null;
|
17
|
+
var recordType = this.recordType, fields = recordType.prototype.fields;
|
18
|
+
var records = [];
|
19
|
+
// console.info(this.meta);
|
20
|
+
var root = o[this.meta.root] || o, totalRecords = o[this.meta.totalProperty], success = o[this.meta.successProperty];
|
21
|
+
for(var i = 0; i < root.length; i++){
|
22
|
+
var n = root[i];
|
23
|
+
var values = {};
|
24
|
+
var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
|
25
|
+
for(var j = 0, jlen = fields.length; j < jlen; j++){
|
26
|
+
var f = fields.items[j];
|
27
|
+
var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
|
28
|
+
var v = n[k] !== undefined ? n[k] : f.defaultValue;
|
29
|
+
v = f.convert(v, n);
|
30
|
+
values[f.name] = v;
|
31
|
+
}
|
32
|
+
var record = new recordType(values, id);
|
33
|
+
record.json = n;
|
34
|
+
records[records.length] = record;
|
35
|
+
}
|
36
|
+
return {
|
37
|
+
records : records,
|
38
|
+
totalRecords : totalRecords,
|
39
|
+
success : success
|
40
|
+
};
|
41
|
+
}
|
42
|
+
});
|
43
|
+
|
44
|
+
// Methods common to all widget classes
|
45
|
+
Ext.widgetMixIn = {
|
46
|
+
widgetInit:function(config){
|
47
|
+
this.app = Ext.getCmp('application');
|
48
|
+
if (config.tools) Ext.each(config.tools, function(i){i.on.click = this[i.on.click].createDelegate(this)}, this);
|
49
|
+
if (config.actions) Ext.each(config.actions, function(i){i.handler = this[i.handler].createDelegate(this);}, this);
|
50
|
+
},
|
51
|
+
|
52
|
+
setEvents: function(){
|
53
|
+
this.on('beforedestroy', function(){
|
54
|
+
// clean-up menus
|
55
|
+
if (this.app && !!this.app.unhostMenus) {
|
56
|
+
// alert('beforedestroy');
|
57
|
+
this.app.unhostMenus(this)
|
58
|
+
}
|
59
|
+
}, this);
|
60
|
+
|
61
|
+
this.on('render', this.onWidgetLoad, this);
|
62
|
+
},
|
63
|
+
|
64
|
+
feedback:function(msg){
|
65
|
+
if (this.app && !!this.app.showFeedback) {
|
66
|
+
this.app.showFeedback(msg)
|
67
|
+
} else {
|
68
|
+
// there's no application to show the feedback - so, we do it ourselves
|
69
|
+
if (typeof msg == 'string'){
|
70
|
+
alert(msg)
|
71
|
+
} else {
|
72
|
+
var compoundResponse = ""
|
73
|
+
Ext.each(msg, function(m){
|
74
|
+
compoundResponse += m.msg + "\n"
|
75
|
+
})
|
76
|
+
if (compoundResponse != "") alert(compoundResponse);
|
77
|
+
}
|
78
|
+
};
|
79
|
+
},
|
80
|
+
|
81
|
+
addMenus:function(menus){
|
82
|
+
if (this.app && !!this.app.hostMenu) {
|
83
|
+
Ext.each(menus, function(menu){this.app.hostMenu(menu, this)}, this)
|
84
|
+
}
|
85
|
+
},
|
86
|
+
|
87
|
+
onWidgetLoad:Ext.emptyFn // gets overridden
|
88
|
+
}
|
89
|
+
|
90
|
+
// Make Panel with layout 'fit' capable to dynamically load widgets
|
91
|
+
Ext.override(Ext.Panel, {
|
92
|
+
getWidget: function(){
|
93
|
+
return this.items.get(0)
|
94
|
+
},
|
95
|
+
|
96
|
+
loadWidget: function(url, params){
|
97
|
+
if (!params) params = {}
|
98
|
+
|
99
|
+
this.remove(this.getWidget()); // first delete previous widget
|
100
|
+
|
101
|
+
// we will let the server know which components we have cached
|
102
|
+
var cachedComponentNames = [];
|
103
|
+
for (name in Ext.componentCache) {
|
104
|
+
cachedComponentNames.push(name);
|
105
|
+
}
|
106
|
+
|
107
|
+
this.disable(); // to visually emphasize loading
|
108
|
+
|
109
|
+
Ext.Ajax.request(
|
110
|
+
{url:url, params:Ext.apply(params, {components_cache:Ext.encode(cachedComponentNames)}), script:false, callback:function(panel, success, response){
|
111
|
+
var response = Ext.decode(response.responseText);
|
112
|
+
if (response['classDefinition']) eval(response['classDefinition']); // evaluate widget's class if it was sent
|
113
|
+
|
114
|
+
response.config.parent = this // we might want to know the parent panel in advance (e.g. to know its size)
|
115
|
+
var instance = new Ext.componentCache[response.config.widgetClassName](response.config)
|
116
|
+
|
117
|
+
this.add(instance);
|
118
|
+
this.doLayout();
|
119
|
+
this.enable();
|
120
|
+
}, scope:this}
|
121
|
+
)
|
122
|
+
|
123
|
+
}
|
124
|
+
})
|
125
|
+
|
126
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class NetzkeController < ActionController::Base
|
2
|
+
|
3
|
+
# collect javascripts from all plugins that registered it in Netzke::Base.config[:javascripts]
|
4
|
+
def netzke
|
5
|
+
respond_to do |format|
|
6
|
+
format.js {
|
7
|
+
res = ""
|
8
|
+
Netzke::Base.config[:javascripts].each do |path|
|
9
|
+
f = File.new(path)
|
10
|
+
res << f.read
|
11
|
+
end
|
12
|
+
render :text => res
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class NetzkeLayout < ActiveRecord::Base
|
2
|
+
# has_many :layout_items#, :class_name => "ExtWidget::LayoutItem", :order => :position
|
3
|
+
# has_many :objects, :class_name => "Objects", :foreign_key => "class_name_id"
|
4
|
+
# belongs_to :role
|
5
|
+
# belongs_to :user
|
6
|
+
|
7
|
+
UNRELATED_ATTRS = %w(created_at updated_at position layout_id)
|
8
|
+
|
9
|
+
def self.user_id
|
10
|
+
@@user_id ||= nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def layout_items
|
14
|
+
items_class.constantize.find_all_by_layout_id(id, :order => 'position')
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# def self.user_id=(user_id)
|
19
|
+
# @@user_id = user_id
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def self.layout_items(widget_name)
|
23
|
+
# layout = self.layout(widget_name)
|
24
|
+
# layout.nil? ? nil : layout.layout_items.map(&:attributes).map{|item| item.delete_if{|k,v| UNRELATED_ATTRS.include?(k)}}
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
def self.by_widget(widget_name)
|
28
|
+
self.find(:first, :conditions => {:widget_name => widget_name, :user_id => self.user_id})
|
29
|
+
end
|
30
|
+
|
31
|
+
def move_item(old_index, new_index)
|
32
|
+
layout_item = layout_items[old_index]
|
33
|
+
layout_item.remove_from_list
|
34
|
+
layout_item.insert_at(new_index + 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
# def self.layout_items(widget_name)
|
38
|
+
# layout = self.by_widget(widget_name)
|
39
|
+
# if layout
|
40
|
+
# layout.layout_items
|
41
|
+
# else
|
42
|
+
# # create new layout and fill it out with default values
|
43
|
+
# layout = Layout.create({:widget_name => widget_name, :user_id => self.user_id})
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
|
47
|
+
def items_hash
|
48
|
+
layout_items.map(&:attributes).map{|item| item.delete_if{|k,v| UNRELATED_ATTRS.include?(k)}}.map{ |i| i.convert_keys{ |k| k.to_sym } }
|
49
|
+
end
|
50
|
+
|
51
|
+
# if layout items are provided, use them instead of defaults (exsposed) layout items, but merge their configs with the default
|
52
|
+
# def self.create_layout_for_widget(widget_name, data_class_name, layout_item_class_name, items = nil)
|
53
|
+
# layout = self.create(:widget_name => widget_name, :items_class => layout_item_class_name, :user_id => self.user_id)
|
54
|
+
# data_class = data_class_name.constantize
|
55
|
+
# layout_item_class = layout_item_class_name.constantize
|
56
|
+
#
|
57
|
+
#
|
58
|
+
# if items.nil?
|
59
|
+
# complete_items = data_class.exposed_columns
|
60
|
+
# else
|
61
|
+
# # normalize columns
|
62
|
+
# columns = columns.
|
63
|
+
# default_columns = data_class.exposed_columns.map{|c| c.is_a?(Symbol) ? {:name => c} : c}
|
64
|
+
# columns.each
|
65
|
+
#
|
66
|
+
# complete_columns = columns.nil? ? : columns
|
67
|
+
# complete_columns.each do |c|
|
68
|
+
# config = c.is_a?(Hash) ? c : {:name => c}
|
69
|
+
# # we have to merge layout_id in order to have :position set up properly
|
70
|
+
# item = layout_item_class.create_with_defaults(config.merge({:layout_id => layout.id}), data_class)
|
71
|
+
# end
|
72
|
+
# layout
|
73
|
+
# end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class NetzkePreference < ActiveRecord::Base
|
2
|
+
CONVERTION_METHODS= {'Fixnum' => 'to_i', 'String' => 'to_s', 'Float' => 'to_f', 'Symbol' => 'to_sym'}
|
3
|
+
|
4
|
+
def self.user=(user)
|
5
|
+
@@user = user
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.user
|
9
|
+
@@user ||= nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.custom_field=(value)
|
13
|
+
@@custom_field = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.custom_field
|
17
|
+
@@custom_field ||= nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def normalized_value
|
21
|
+
klass = read_attribute(:pref_type)
|
22
|
+
norm_value = read_attribute(:value)
|
23
|
+
if klass.nil?
|
24
|
+
# do not cast
|
25
|
+
r = norm_value
|
26
|
+
elsif klass == 'Boolean'
|
27
|
+
r = norm_value == 'false' ? false : (norm_value == 'true' || norm_value)
|
28
|
+
elsif klass == 'NilClass'
|
29
|
+
r = nil
|
30
|
+
elsif klass == 'Array'
|
31
|
+
r = JSON.parse(norm_value)
|
32
|
+
else
|
33
|
+
r = norm_value.send(CONVERTION_METHODS[klass])
|
34
|
+
end
|
35
|
+
r
|
36
|
+
end
|
37
|
+
|
38
|
+
def normalized_value=(new_value)
|
39
|
+
# norm_value = (new_value.to_s if new_value == true or new_value == false) || new_value
|
40
|
+
case new_value.class.to_s
|
41
|
+
when "Array"
|
42
|
+
write_attribute(:value, new_value.to_json)
|
43
|
+
else
|
44
|
+
write_attribute(:value, new_value.to_s)
|
45
|
+
end
|
46
|
+
write_attribute(:pref_type, [TrueClass, FalseClass].include?(new_value.class) ? 'Boolean' : new_value.class.to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.[](pref_name)
|
50
|
+
pref_name = pref_name.to_s
|
51
|
+
conditions = {:name => pref_name, :user_id => self.user, :custom_field => self.custom_field}
|
52
|
+
pref = self.find(:first, :conditions => conditions)
|
53
|
+
# pref = @@user.nil? ? self.find_by_name(pref_name) : self.find_by_name_and_user_id(pref_name, @@user.id)
|
54
|
+
pref && pref.normalized_value
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.[]=(pref_name, new_value)
|
58
|
+
pref_name = pref_name.to_s
|
59
|
+
conditions = {:name => pref_name, :user_id => self.user, :custom_field => self.custom_field}
|
60
|
+
pref = self.find(:first, :conditions => conditions) || self.create(conditions)
|
61
|
+
# pref = self.user.nil? ? self.find_or_create_by_name(pref_name) : self.find_or_create_by_name_and_user_id(pref_name, self.user.id)
|
62
|
+
pref.normalized_value = new_value
|
63
|
+
pref.save!
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/netzke/base.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'json'
|
2
|
+
module Netzke
|
3
|
+
class Base
|
4
|
+
# Global Netzke configuration
|
5
|
+
def self.config
|
6
|
+
@@config ||= {
|
7
|
+
:javascripts => ["#{File.dirname(__FILE__)}/../../javascripts/core.js"] # locations of javascript files (which automatically will be collected into one file and sent as netzke.js)
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Helper class to read/write from/to widget's persistent preferences. TODO: rework it.
|
12
|
+
class Config
|
13
|
+
def initialize(widget_name)
|
14
|
+
@widget_name = widget_name
|
15
|
+
end
|
16
|
+
def []=(k,v)
|
17
|
+
NetzkePreference.custom_field = @widget_name
|
18
|
+
NetzkePreference[k] = v
|
19
|
+
end
|
20
|
+
def [](k)
|
21
|
+
NetzkePreference.custom_field = @widget_name
|
22
|
+
NetzkePreference[k]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# client-side code (generates JS-classes of the widgets)
|
27
|
+
include Netzke::JsClassBuilder
|
28
|
+
|
29
|
+
attr_accessor :config, :server_confg, :parent, :logger, :id_name, :permissions
|
30
|
+
attr_reader :pref
|
31
|
+
|
32
|
+
def initialize(config = {}, parent = nil)
|
33
|
+
@logger = Logger.new("debug.log")
|
34
|
+
@config = initial_config.recursive_merge(config)
|
35
|
+
@parent = parent
|
36
|
+
@id_name = parent.nil? ? config[:name].to_s : "#{parent.id_name}__#{config[:name]}"
|
37
|
+
|
38
|
+
@flash = []
|
39
|
+
@pref = Config.new(@id_name)
|
40
|
+
|
41
|
+
@config[:ext_config] ||= {} # configuration used to instantiate JS class
|
42
|
+
|
43
|
+
process_permissions_config
|
44
|
+
end
|
45
|
+
|
46
|
+
def initial_config
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
|
50
|
+
# 'Netzke::Grid' => 'Grid'
|
51
|
+
def short_widget_class_name
|
52
|
+
self.class.short_widget_class_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.short_widget_class_name
|
56
|
+
name.split("::").last
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Use this class-method to declare connection points between client side of a widget and its server side. A method in a widget class with the same name will be (magically) called by the client-side of the widget. See Grid widget for an example
|
61
|
+
#
|
62
|
+
def self.interface(*interface_points)
|
63
|
+
interfacep = read_inheritable_attribute(:interface_points) || []
|
64
|
+
interface_points.each{|p| interfacep << p}
|
65
|
+
write_inheritable_attribute(:interface_points, interfacep)
|
66
|
+
|
67
|
+
interface_points.each do |interfacep|
|
68
|
+
module_eval <<-END, __FILE__, __LINE__
|
69
|
+
def interface_#{interfacep}(*args)
|
70
|
+
#{interfacep}(*args).to_js
|
71
|
+
end
|
72
|
+
# FIXME: commented out because otherwise ColumnOperations stop working
|
73
|
+
# def #{interfacep}(*args)
|
74
|
+
# flash :warning => "API point '#{interfacep}' is not implemented for widget '#{short_widget_class_name}'"
|
75
|
+
# {:flash => @flash}
|
76
|
+
# end
|
77
|
+
END
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.interface_points
|
82
|
+
read_inheritable_attribute(:interface_points)
|
83
|
+
end
|
84
|
+
|
85
|
+
def interface_points
|
86
|
+
self.class.interface_points
|
87
|
+
end
|
88
|
+
|
89
|
+
interface :get_widget # default
|
90
|
+
|
91
|
+
## Dependencies
|
92
|
+
def dependencies
|
93
|
+
@dependencies ||= initial_dependencies
|
94
|
+
end
|
95
|
+
|
96
|
+
def initial_dependencies
|
97
|
+
config[:dependencies] || []
|
98
|
+
end
|
99
|
+
|
100
|
+
### Aggregation
|
101
|
+
def initial_aggregatees
|
102
|
+
{}
|
103
|
+
end
|
104
|
+
|
105
|
+
def aggregatees
|
106
|
+
@aggregatees ||= initial_aggregatees.merge(initial_late_aggregatees.each_pair{|k,v| v.merge!(:late_aggregation => true)})
|
107
|
+
end
|
108
|
+
|
109
|
+
def add_aggregatee(aggr)
|
110
|
+
aggregatees.merge!(aggr)
|
111
|
+
end
|
112
|
+
|
113
|
+
# 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. While a late aggregatee doesn't get instantiated along with its aggregator. Until it gets requested, it doesn't take any part in its aggregator's lifecycle. An example of late aggregatee could be a widget that is loaded by an application widget on user's request, or a preferences window that only gets instantiated when user wants to edit widget's preferences. An example of a normal aggregatee is any widget (like a grid) within a BorderLayout-based widget (i.e. aggregator) - it should get instantiated and shown along with its aggregator.
|
114
|
+
def initial_late_aggregatees
|
115
|
+
{}
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_late_aggregatee(aggr)
|
119
|
+
aggregatees.merge!(aggr.merge(:late_aggregation => true))
|
120
|
+
end
|
121
|
+
|
122
|
+
# 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"
|
123
|
+
def aggregatee_instance(name)
|
124
|
+
aggregator = self
|
125
|
+
name.to_s.split('__').each do |aggr|
|
126
|
+
aggr = aggr.to_sym
|
127
|
+
# TODO: should we put all the classes under Netzke::-scope?
|
128
|
+
# widget_class = full_widget_class_name(aggregator.aggregatees[aggr][:widget_class_name]).constantize
|
129
|
+
widget_class = "Netzke::#{aggregator.aggregatees[aggr][:widget_class_name]}".constantize
|
130
|
+
aggregator = widget_class.new(aggregator.aggregatees[aggr].merge(:name => aggr), aggregator)
|
131
|
+
end
|
132
|
+
aggregator
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def full_widget_class_name(short_name)
|
137
|
+
"Netzke::#{short_name}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def flash(flash_hash)
|
141
|
+
level = flash_hash.keys.first
|
142
|
+
raise "Unknown message level for flash" unless %(notice warning error).include?(level.to_s)
|
143
|
+
@flash << {:level => level, :msg => flash_hash[level]}
|
144
|
+
end
|
145
|
+
|
146
|
+
def widget_action(action_name)
|
147
|
+
"#{@id_name}__#{action_name}"
|
148
|
+
end
|
149
|
+
|
150
|
+
# permissions
|
151
|
+
def available_permissions
|
152
|
+
[]
|
153
|
+
end
|
154
|
+
|
155
|
+
def process_permissions_config
|
156
|
+
if !available_permissions.empty?
|
157
|
+
# First, process permissions from the config
|
158
|
+
@permissions = available_permissions.inject({}){|h,p| h.merge(p.to_sym => true)} # by default anything is allowed
|
159
|
+
|
160
|
+
config[:prohibit] = available_permissions if config[:prohibit] == :all # short-cut for all permissions
|
161
|
+
config[:prohibit] = [config[:prohibit]] if config[:prohibit].is_a?(Symbol) # so that config[:prohibit] => :write works
|
162
|
+
config[:prohibit] && config[:prohibit].each{|p| @permissions.merge!(p.to_sym => false)} # prohibit
|
163
|
+
|
164
|
+
config[:allow] = [config[:allow]] if config[:allow].is_a?(Symbol) # so that config[:allow] => :write works
|
165
|
+
config[:allow] && config[:allow].each{|p| @permissions.merge!(p.to_sym => true)} # allow
|
166
|
+
|
167
|
+
# ... and then merge it with NetzkePreferences
|
168
|
+
available_permissions.each do |p|
|
169
|
+
@permissions[p.to_sym] = @pref["permissions.#{p}"] if !@pref["permissions.#{p}"].nil?
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
## method dispatcher - sends method to the proper aggregatee
|
175
|
+
def method_missing(method_name, params = {})
|
176
|
+
widget, *action = method_name.to_s.split('__')
|
177
|
+
widget = widget.to_sym
|
178
|
+
action = !action.empty? && action.join("__").to_sym
|
179
|
+
|
180
|
+
if action && aggregatees[widget]
|
181
|
+
# only actions starting with "interface_" are accessible
|
182
|
+
interface_action = action.to_s.index('__') ? action : "interface_#{action}"
|
183
|
+
aggregatee_instance(widget).send(interface_action, params)
|
184
|
+
else
|
185
|
+
super
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
#### API section
|
190
|
+
def get_widget(params = {})
|
191
|
+
# if browser does not have our component class cached (and all dependencies), send it to him
|
192
|
+
components_cache = (JSON.parse(params[:components_cache]) if params[:components_cache]) || []
|
193
|
+
|
194
|
+
{:config => js_config, :class_definition => js_missing_code(components_cache)}
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|