skozlov-netzke-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/CHANGELOG +3 -0
  2. data/LICENSE +20 -0
  3. data/Rakefile +15 -0
  4. data/generators/netzke_core/USAGE +8 -0
  5. data/generators/netzke_core/netzke_core_generator.rb +13 -0
  6. data/generators/netzke_core/templates/create_netzke_layouts.rb +14 -0
  7. data/generators/netzke_core/templates/create_netzke_preferences.rb +18 -0
  8. data/generators/netzke_core/templates/netzke.html.erb +10 -0
  9. data/init.rb +1 -0
  10. data/install.rb +1 -0
  11. data/javascripts/core.js +126 -0
  12. data/lib/app/controllers/netzke_controller.rb +16 -0
  13. data/lib/app/models/netzke_layout.rb +75 -0
  14. data/lib/app/models/netzke_preference.rb +66 -0
  15. data/lib/netzke/base.rb +198 -0
  16. data/lib/netzke/controller_extensions.rb +87 -0
  17. data/lib/netzke/core_ext.rb +77 -0
  18. data/lib/netzke/js_class_builder.rb +125 -0
  19. data/lib/netzke-core.rb +22 -0
  20. data/lib/vendor/facets/hash/recursive_merge.rb +28 -0
  21. data/netzke-core.gemspec +32 -0
  22. data/tasks/netzke_core_tasks.rake +4 -0
  23. data/test/app_root/app/controllers/application.rb +2 -0
  24. data/test/app_root/config/boot.rb +114 -0
  25. data/test/app_root/config/database.yml +21 -0
  26. data/test/app_root/config/environment.rb +13 -0
  27. data/test/app_root/config/environments/in_memory.rb +0 -0
  28. data/test/app_root/config/environments/mysql.rb +0 -0
  29. data/test/app_root/config/environments/postgresql.rb +0 -0
  30. data/test/app_root/config/environments/sqlite.rb +0 -0
  31. data/test/app_root/config/environments/sqlite3.rb +0 -0
  32. data/test/app_root/config/routes.rb +4 -0
  33. data/test/app_root/script/console +6 -0
  34. data/test/core_ext_test.rb +35 -0
  35. data/test/netzke_core_test.rb +136 -0
  36. data/test/test_helper.rb +20 -0
  37. data/uninstall.rb +1 -0
  38. metadata +109 -0
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ v0.1.0.1 Meta work: replacing underscore with dash in the name
2
+
3
+ v0.1.0 Initial release
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,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ ./script/generate netzke-core Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -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
@@ -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
@@ -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