netzke-basepack 0.5.8 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/CHANGELOG.rdoc +18 -0
  2. data/README.rdoc +11 -4
  3. data/Rakefile +19 -3
  4. data/TODO.rdoc +1 -1
  5. data/generators/netzke_basepack/netzke_basepack_generator.rb +13 -0
  6. data/generators/netzke_basepack/templates/create_netzke_field_lists.rb +18 -0
  7. data/generators/netzke_basepack/templates/public_assets/ts-checkbox.gif +0 -0
  8. data/install.rb +1 -1
  9. data/javascripts/basepack.js +124 -30
  10. data/lib/app/models/netzke_field_list.rb +261 -0
  11. data/lib/app/models/netzke_model_attr_list.rb +21 -0
  12. data/lib/app/models/netzke_persistent_array_auto_model.rb +58 -0
  13. data/lib/netzke-basepack.rb +17 -2
  14. data/lib/netzke/active_record.rb +10 -0
  15. data/lib/netzke/active_record/association_attributes.rb +102 -0
  16. data/lib/netzke/active_record/attributes.rb +100 -0
  17. data/lib/netzke/active_record/combobox_options.rb +43 -0
  18. data/lib/netzke/active_record/data_accessor.rb +9 -12
  19. data/lib/netzke/attributes_configurator.rb +195 -0
  20. data/lib/netzke/basic_app.rb +47 -4
  21. data/lib/netzke/configuration_panel.rb +1 -1
  22. data/lib/netzke/data_accessor.rb +7 -30
  23. data/lib/netzke/fields_configurator.rb +106 -41
  24. data/lib/netzke/form_panel.rb +28 -125
  25. data/lib/netzke/form_panel/form_panel_api.rb +2 -3
  26. data/lib/netzke/form_panel/form_panel_fields.rb +147 -0
  27. data/lib/netzke/form_panel/form_panel_js.rb +35 -15
  28. data/lib/netzke/grid_panel.rb +130 -213
  29. data/lib/netzke/grid_panel/grid_panel_api.rb +254 -257
  30. data/lib/netzke/grid_panel/grid_panel_columns.rb +226 -0
  31. data/lib/netzke/grid_panel/grid_panel_js.rb +126 -119
  32. data/lib/netzke/grid_panel/record_form_window.rb +7 -1
  33. data/lib/netzke/json_array_editor.rb +61 -0
  34. data/lib/netzke/plugins/configuration_tool.rb +1 -1
  35. data/lib/netzke/property_editor.rb +3 -3
  36. data/lib/netzke/search_panel.rb +164 -27
  37. data/lib/netzke/tab_panel.rb +14 -12
  38. data/stylesheets/basepack.css +43 -2
  39. data/test/app_root/app/models/book.rb +1 -1
  40. data/test/app_root/app/models/role.rb +3 -0
  41. data/test/app_root/app/models/user.rb +3 -0
  42. data/test/app_root/config/database.yml +1 -1
  43. data/test/app_root/db/migrate/20090102223630_create_netzke_field_lists.rb +18 -0
  44. data/test/app_root/db/migrate/20090423214303_create_roles.rb +11 -0
  45. data/test/app_root/db/migrate/20090423222114_create_users.rb +12 -0
  46. data/test/fixtures/books.yml +4 -2
  47. data/test/fixtures/categories.yml +2 -2
  48. data/test/fixtures/genres.yml +6 -6
  49. data/test/fixtures/roles.yml +8 -0
  50. data/test/fixtures/users.yml +11 -0
  51. data/test/test_helper.rb +2 -0
  52. data/test/unit/active_record_basepack_test.rb +2 -2
  53. data/test/unit/fields_configuration_test.rb +18 -0
  54. data/test/unit/grid_panel_test.rb +29 -27
  55. metadata +41 -16
  56. data/lib/app/models/netzke_auto_column.rb +0 -4
  57. data/lib/app/models/netzke_auto_field.rb +0 -4
  58. data/lib/app/models/netzke_auto_table.rb +0 -61
  59. data/lib/netzke/active_record/basepack.rb +0 -134
  60. data/lib/netzke/grid_panel/javascripts/filters.js +0 -7
  61. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +0 -14
  62. data/test/unit/helper_model_test.rb +0 -30
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,21 @@
1
+ = v0.6.0 - EDGE
2
+ * !!!: after updating to this version, run script/generate netzke_basepack
3
+ * New: tri-state checkbox introduced into the search form
4
+ * Impr: better defaults for the search form
5
+ * Impr: GridPanel now displays the total amount of records
6
+ * Fix: GridPanel's "local filters" are back (they were out since the release of Ext 3.0)
7
+ * Impr: obey "preloaded" option for tabs better: really add the widgets into their respective tabs from the start
8
+ * Fix: datetime filters now also are take into account when searching in GridPanel is performed
9
+ * Fix: FieldsConfigurator should now work on Heroku
10
+ * Fix: GridPanel date filter now should work
11
+ * New: GridPanel now has a method on_data_changed that can be overridden by children to detect actions that modify data
12
+ * New: New way of Netzke virtual attributes in AR models.
13
+ * New: Grid/FormPanel refactoring.
14
+ * Impr: Multi-level column/fields configuration reworked and made more consistent.
15
+ * New: New configuration layer introduced between the AR models and the rest of Netzke widgets. Use AttributesConfigurator to access it.
16
+ * New: FamFamFam Silk icons support.
17
+ * Impr: Reworked Netzke (virtual) attributes for Grid/FormPanel
18
+
1
19
  = v0.5.8 - 2010-03-12
2
20
  * Fix: GertThiel's method_missing-related fix enabling better compatibility with other Ruby libs
3
21
  * Fix: acts_as_list runtime dependency
data/README.rdoc CHANGED
@@ -1,7 +1,7 @@
1
1
  = netzke-basepack
2
2
  A pack of basic Rails/ExtJS widgets as a part of the Netzke framework. Live demo/tutorials on http://blog.writelesscode.com. Introduction to the Netzke framework and the Wiki: http://github.com/skozlov/netzke
3
3
 
4
- = Prerequisites
4
+ == Prerequisites
5
5
  1. Rails >= 2.2
6
6
  2. Netzke assumes that your ExtJS library is in public/extjs, which may be a symbolic link, e.g:
7
7
 
@@ -28,7 +28,7 @@ Or install as gem:
28
28
 
29
29
  gem install netzke-basepack
30
30
 
31
- = Usage
31
+ == Usage
32
32
  If using as gem, include it into environment.rb:
33
33
 
34
34
  config.gem "netzke-basepack"
@@ -37,10 +37,14 @@ Include mapping for Netzke controller providing Netzke javascripts and styles (i
37
37
 
38
38
  map.netzke
39
39
 
40
- To be able to use persistent on-the-fly configuration of widgets, run netzke-core generators to have the necessary migrations:
40
+ Run the generators (some assets and a migration for dynamically configured columns/fields):
41
+
42
+ ./script/generate netzke_basepack
41
43
 
42
- ./script/generate netzke_core
44
+ To be able to use persistent on-the-fly configuration of widgets, run netzke-core generators:
43
45
 
46
+ ./script/generate netzke_core
47
+
44
48
  ... then run the migrations:
45
49
 
46
50
  rake db:migrate
@@ -64,6 +68,9 @@ Now embed a widget into a view like this:
64
68
 
65
69
  For more examples, see http://netzke-demo.writelesscode.com
66
70
 
71
+ == Icons support
72
+ Netzke-basepack supports FamFamFam Silk icon set (http://www.famfamfam.com/archive/silk-icons-thats-your-lot/). To enable it, download it and put the "icons" folder into your app's public/images folder. Then restart your application.
73
+
67
74
  == More info
68
75
  Introduction to Netzke framework and wiki: http://github.com/skozlov/netzke
69
76
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  begin
2
2
  require 'jeweler'
3
3
  Jeweler::Tasks.new do |gemspec|
4
- gemspec.version = "0.5.8"
4
+ gemspec.version = "0.5.9"
5
5
  gemspec.name = "netzke-basepack"
6
6
  gemspec.summary = "Pre-built Rails + ExtJS widgets for your RIA"
7
7
  gemspec.description = "A set of full-featured extendible Netzke widgets (such as FormPanel, GridPanel, Window, BorderLayoutPanel, etc) which can be used as building block for your RIA"
@@ -9,14 +9,30 @@ begin
9
9
  gemspec.homepage = "http://github.com/skozlov/netzke-basepack"
10
10
  gemspec.rubyforge_project = "netzke-basepack"
11
11
  gemspec.authors = ["Sergei Kozlov"]
12
- gemspec.add_dependency("netzke-core", ">=0.5.1")
12
+ gemspec.add_dependency("netzke-core", ">=0.5.2")
13
13
  gemspec.add_dependency("searchlogic", ">=2.0.0")
14
14
  gemspec.add_dependency("will_paginate", ">=2.0.0")
15
15
  gemspec.add_dependency("acts_as_list", ">=0.1.2")
16
+ gemspec.post_install_message = <<-MESSAGE
17
+
18
+ ========================================================================
19
+
20
+ Thanks for installing Netzke Basepack!
21
+
22
+ Run "./script/generate netzke_basepack" to finish the installation.
23
+
24
+ Netzke home page: http://netzke.org
25
+ Netzke Google Groups: http://groups.google.com/group/netzke
26
+ Netzke tutorials: http://blog.writelesscode.com
27
+
28
+ ========================================================================
29
+
30
+ MESSAGE
31
+
16
32
  end
17
33
  Jeweler::GemcutterTasks.new
18
34
  rescue LoadError
19
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
35
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
36
  end
21
37
 
22
38
  require 'rake/rdoctask'
data/TODO.rdoc CHANGED
@@ -1,10 +1,10 @@
1
1
  == Priority
2
- * Introduce three-state checkbox for SearchPanel
3
2
  * Add icons to buttons to actions (toolbars/menus)
4
3
  * On grid refresh, reset the dirty fields, so that the "Apply" button doesn't do anything
5
4
 
6
5
  == Foolproof
7
6
  * Should not be possible delete the "ID" field from grids/forms
7
+ * Should not be possible to put the "ID" field on any place but first for grids (otherwise record ID is not correct)
8
8
 
9
9
  == Optimizations
10
10
  * Check persistent_config-related queries (aren't they too many?)
@@ -0,0 +1,13 @@
1
+ # class NetzkeCoreGenerator < Rails::Generator::NamedBase
2
+ class NetzkeBasepackGenerator < Rails::Generator::Base
3
+ def manifest
4
+ record do |m|
5
+ m.directory 'public/netzke/basepack'
6
+ m.file 'public_assets/ts-checkbox.gif', "public/netzke/basepack/ts-checkbox.gif"
7
+
8
+ m.migration_template 'create_netzke_field_lists.rb', 'db/migrate', :assigns => {
9
+ :migration_name => "CreateNetzkeFieldLists"
10
+ }, :migration_file_name => "create_netzke_field_lists"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ class CreateNetzkeFieldLists < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :netzke_field_lists do |t|
4
+ t.string :name
5
+ t.text :value
6
+ t.string :model_name
7
+ t.integer :user_id
8
+ t.integer :role_id
9
+ t.string :type
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ drop_table :netzke_field_lists
17
+ end
18
+ end
data/install.rb CHANGED
@@ -1 +1 @@
1
- # Install hook code here
1
+ # Install hook code here
@@ -6,7 +6,7 @@ Ext.netzke.PassField = Ext.extend(Ext.form.TextField, {
6
6
  });
7
7
  Ext.reg('passfield', Ext.netzke.PassField);
8
8
 
9
- // Combobox that knows to talk to the server side (used in both grids and panels)
9
+ // ComboBox that gets options from the server (used in both grids and panels)
10
10
  Ext.netzke.ComboBox = Ext.extend(Ext.form.ComboBox, {
11
11
  displayField : 'id',
12
12
  valueField : 'id',
@@ -14,29 +14,22 @@ Ext.netzke.ComboBox = Ext.extend(Ext.form.ComboBox, {
14
14
  typeAhead : true,
15
15
 
16
16
  initComponent : function(){
17
- if (this.options) {
18
- Ext.apply(this, {
19
- store : this.options,
20
- mode : "local"
21
- });
22
- } else {
23
- var row = Ext.data.Record.create([{name:'id'}]);
24
- var store = new Ext.data.Store({
25
- proxy : new Ext.data.HttpProxy({url: Ext.getCmp(this.parentId).buildApiUrl("get_combobox_options"), jsonData:{column:this.name}}),
26
- reader : new Ext.data.ArrayReader({root:'data', id:0}, row)
27
- });
28
-
29
- Ext.apply(this, {
30
- store : store
31
- });
32
- }
17
+ var row = Ext.data.Record.create([{name:'id'}]);
18
+ var store = new Ext.data.Store({
19
+ proxy : new Ext.data.HttpProxy({url: Ext.getCmp(this.parentId).buildApiUrl("get_combobox_options"), jsonData:{column:this.name}}),
20
+ reader : new Ext.data.ArrayReader({root:'data', id:0}, row)
21
+ });
22
+
23
+ Ext.apply(this, {
24
+ store : store
25
+ });
33
26
 
34
27
  Ext.netzke.ComboBox.superclass.initComponent.apply(this, arguments);
35
28
 
36
29
  this.on('blur', function(cb){
37
30
  cb.setValue(cb.getRawValue());
38
31
  });
39
-
32
+
40
33
  this.on('specialkey', function(cb, event){
41
34
  if (event.getKey() == 9 || event.getKey() == 13) {cb.setValue(cb.getRawValue());}
42
35
  });
@@ -85,17 +78,6 @@ Ext.util.Format.mask = function(v){
85
78
  return "********";
86
79
  };
87
80
 
88
- // Mapping of editor field to grid filters
89
- Ext.netzke.filterMap = {
90
- numberfield:'Numeric',
91
- textfield:'String',
92
- textarea:'String',
93
- xdatetime:'String',
94
- checkbox:'Boolean',
95
- combobox:'String',
96
- datefield:'Date'
97
- };
98
-
99
81
  Ext.data.RecordArrayReader = Ext.extend(Ext.data.JsonReader, {
100
82
  /**
101
83
  * Create a data block containing Ext.data.Records from an Array.
@@ -865,4 +847,116 @@ Ext.override(Ext.Panel, {
865
847
  this.syncShadow();
866
848
  Ext.Panel.superclass.onResize.call(this, w, h, rw, rh);
867
849
  }
868
- });
850
+ });
851
+
852
+ Ext.ns('Ext.ux.form');
853
+ Ext.ux.form.TriCheckbox = Ext.extend(Ext.form.Checkbox, {
854
+ checked: null,
855
+ valueList: [null, false, true],
856
+ stateClassList: ['x-checkbox-undef', null, 'x-checkbox-checked'],
857
+ overClass: 'x-form-check-over',
858
+ clickClass: 'x-form-check-down',
859
+ triState: true,
860
+ defaultAutoCreate: {tag: 'input', type: 'hidden', autocomplete: 'off'},
861
+ initComponent: function() {
862
+ this.value = this.checked;
863
+ Ext.ux.form.TriCheckbox.superclass.initComponent.apply(this, arguments);
864
+ // make a copy before modifying valueList and stateClassList arrays
865
+ this.vList = this.valueList.slice(0);
866
+ this.cList = this.stateClassList.slice(0);
867
+ if(this.triState !== true) {
868
+ // consider 'undefined' value and its class go first in arrays
869
+ this.vList.shift();
870
+ this.cList.shift();
871
+ }
872
+ if(this.overCls !== undefined) {
873
+ this.overClass = this.overCls;
874
+ delete this.overCls;
875
+ }
876
+ this.value = this.normalizeValue(this.value);
877
+ },
878
+ onRender : function(ct, position){
879
+ Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
880
+
881
+ this.innerWrap = this.el.wrap({tag: 'span', cls: 'x-form-check-innerwrap'});
882
+ this.wrap = this.innerWrap.wrap({cls: 'x-form-check-wrap'});
883
+
884
+ this.currCls = this.getCls(this.value);
885
+ this.wrap.addClass(this.currCls);
886
+ if(this.clickClass && !this.disabled && !this.readOnly)
887
+ this.innerWrap.addClassOnClick(this.clickClass);
888
+ if(this.overClass && !this.disabled && !this.readOnly)
889
+ this.innerWrap.addClassOnOver(this.overClass);
890
+
891
+ this.imageEl = this.innerWrap.createChild({
892
+ tag: 'img',
893
+ src: Ext.BLANK_IMAGE_URL,
894
+ cls: 'x-form-tscheckbox'
895
+ }, this.el);
896
+ if(this.fieldClass) this.imageEl.addClass(this.fieldClass);
897
+
898
+ if(this.boxLabel){
899
+ this.innerWrap.createChild({
900
+ tag: 'label',
901
+ htmlFor: this.el.id,
902
+ cls: 'x-form-cb-label',
903
+ html: this.boxLabel
904
+ });
905
+ }
906
+
907
+ // Need to repaint for IE, otherwise positioning is broken
908
+ if(Ext.isIE){
909
+ this.wrap.repaint();
910
+ }
911
+ this.resizeEl = this.positionEl = this.wrap;
912
+ },
913
+ onResize : function(){
914
+ Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
915
+ if(!this.boxLabel && !this.fieldLabel && this.imageEl){
916
+ this.imageEl.alignTo(this.wrap, 'c-c');
917
+ }
918
+ },
919
+ initEvents : function(){
920
+ Ext.form.Checkbox.superclass.initEvents.call(this);
921
+ this.mon(this.innerWrap, {
922
+ scope: this,
923
+ click: this.onClick
924
+ });
925
+ },
926
+ onClick : function(){
927
+ if (!this.disabled && !this.readOnly) {
928
+ this.setValue(this.vList[(this.vList.indexOf(this.value) + 1) % this.vList.length]);
929
+ }
930
+ },
931
+ getValue : function(){
932
+ return this.value;
933
+ },
934
+ setValue : function(v){
935
+ var value = this.value;
936
+ this.value = this.normalizeValue(v);
937
+ if(this.rendered) this.el.dom.value = this.value;
938
+
939
+ if(value !== this.value){
940
+ this.updateView();
941
+ this.fireEvent('check', this, this.value);
942
+ if(this.handler) this.handler.call(this.scope || this, this, this.value);
943
+ }
944
+ return this;
945
+ },
946
+ normalizeValue: function(v) {
947
+ return (v === null || v === undefined) && this.triState ? null :
948
+ (v === true || (['true', 'yes', 'on', '1']).indexOf(String(v).toLowerCase()) != -1);
949
+ },
950
+ getCls: function(v) {
951
+ var idx = this.vList.indexOf(this.value);
952
+ return idx > -1 ? this.cList[idx] : undefined;
953
+ },
954
+ updateView: function() {
955
+ var cls = this.getCls(this.value);
956
+ if (!this.wrap || cls === undefined) return;
957
+
958
+ this.wrap.replaceClass(this.currCls, cls);
959
+ this.currCls = cls;
960
+ }
961
+ });
962
+ Ext.reg('tricheckbox', Ext.ux.form.TriCheckbox);
@@ -0,0 +1,261 @@
1
+ # TODO: clean up, document and test
2
+ class NetzkeFieldList < ActiveRecord::Base
3
+ belongs_to :user
4
+ belongs_to :role
5
+ belongs_to :parent, :class_name => "NetzkeFieldList"
6
+ has_many :children, :class_name => "NetzkeFieldList", :foreign_key => "parent_id"
7
+
8
+
9
+ def self.update_fields(owner_id, attrs_hash)
10
+ self.find_all_below_current_authority_level(owner_id).each do |list|
11
+ list.update_attrs(attrs_hash)
12
+ end
13
+ end
14
+
15
+ # Updates attributes in the list
16
+ def update_attrs(attrs_hash)
17
+ list = ActiveSupport::JSON.decode(self.value)
18
+ list.each do |field|
19
+ field.merge!(attrs_hash[field["name"]]) if attrs_hash[field["name"]]
20
+ end
21
+ update_attribute(:value, list.to_json)
22
+ end
23
+
24
+ def append_attr(attr_hash)
25
+ list = ActiveSupport::JSON.decode(self.value)
26
+ list << attr_hash
27
+ update_attribute(:value, list.to_json)
28
+ end
29
+
30
+ def self.find_all_below_current_authority_level(pref_name)
31
+ authority_level, authority_id = Netzke::Base.authority_level
32
+ case authority_level
33
+ when :world
34
+ self.all(:conditions => {:name => pref_name})
35
+ when :role
36
+ role = Role.find(authority_id)
37
+ role.users.inject([]) do |r, user|
38
+ r += self.all(:conditions => {:user_id => user.id, :name => pref_name})
39
+ end
40
+ else
41
+ []
42
+ end
43
+ end
44
+
45
+ def self.find_all_lists_under_current_authority(model_name)
46
+ authority_level, authority_id = Netzke::Base.authority_level
47
+ case authority_level
48
+ when :world
49
+ self.all(:conditions => {:model_name => model_name})
50
+ when :role
51
+ role = Role.find(authority_id)
52
+ role.users.inject([]) do |r, user|
53
+ r += self.all(:conditions => {:user_id => user.id, :model_name => model_name})
54
+ end
55
+ when :user
56
+ self.all(:conditions => {:user_id => authority_id, :model_name => model_name})
57
+ when :self
58
+ self.all(:conditions => {:user_id => authority_id, :model_name => model_name})
59
+ else
60
+ []
61
+ end
62
+
63
+ end
64
+
65
+
66
+ # Replaces the list with the data - only for the list found for the current authority.
67
+ # If the list is not found, it's created.
68
+ def self.update_list_for_current_authority(pref_name, data, model_name = nil)
69
+ pref = find_or_create_pref_to_read(pref_name)
70
+ pref.value = data.to_json
71
+ pref.model_name = model_name
72
+ pref.save!
73
+ end
74
+
75
+
76
+ # If the <tt>model</tt> param is provided, then this preference will be assigned a parent preference
77
+ # that configures the attributes for that model. This way we can track all preferences related to a model.
78
+ def self.write_list(name, list, model = nil)
79
+ pref_to_store_the_list = self.pref_to_write(name)
80
+ pref_to_store_the_list.try(:update_attribute, :value, list.to_json)
81
+
82
+ # link this preference to the parent that contains default attributes for the same model
83
+ if model
84
+ model_level_attrs_pref = self.pref_to_read("#{model.tableize}_model_attrs")
85
+ model_level_attrs_pref.children << pref_to_store_the_list if model_level_attrs_pref && pref_to_store_the_list
86
+ end
87
+ end
88
+
89
+ def self.read_list(name)
90
+ json_encoded_value = self.pref_to_read(name).try(:value)
91
+ ActiveSupport::JSON.decode(json_encoded_value).map(&:symbolize_keys) if json_encoded_value
92
+ end
93
+
94
+ # Read model-level attrs
95
+ # def self.read_attrs_for_model(model_name)
96
+ # read_list(model_name)
97
+ # # read_list("#{model.tableize}_model_attrs")
98
+ # end
99
+
100
+ # Write model-level attrs
101
+ # def self.write_attrs_for_model(model_name, data)
102
+ # # write_list("#{model_name.tableize}_model_attrs", data)
103
+ # write_list(model_name, data)
104
+ # end
105
+
106
+ # Options:
107
+ # :attr - attribute to propagate. If not specified, all attrs found in configuration for the model
108
+ # will be propagated.
109
+ def self.update_children_on_attr(model, options = {})
110
+ attr_name = options[:attr].try(:to_s)
111
+
112
+ parent_pref = pref_to_read("#{model.tableize}_model_attrs")
113
+
114
+ if parent_pref
115
+ parent_list = ActiveSupport::JSON.decode(parent_pref.value)
116
+ parent_pref.children.each do |ch|
117
+ child_list = ActiveSupport::JSON.decode(ch.value)
118
+
119
+ if attr_name
120
+ # propagate a certain attribute
121
+ propagate_attr(attr_name, parent_list, child_list)
122
+ else
123
+ # propagate all attributes found in parent
124
+ all_attrs = parent_list.first.try(:keys)
125
+ all_attrs && all_attrs.each{ |attr_name| propagate_attr(attr_name, parent_list, child_list) }
126
+ end
127
+
128
+ ch.update_attribute(:value, child_list.to_json)
129
+ end
130
+ end
131
+ end
132
+
133
+ # meta_attrs:
134
+ # {"city"=>{"included"=>true}, "building_number"=>{"default_value"=>100}}
135
+ def self.update_children(model, meta_attrs)
136
+ parent_pref = pref_to_read("#{model.tableize}_model_attrs")
137
+
138
+
139
+ if parent_pref
140
+ parent_pref.children.each do |ch|
141
+ child_list = ActiveSupport::JSON.decode(ch.value)
142
+
143
+ meta_attrs.each_pair do |k,v|
144
+ child_list.detect{ |child_attr| child_attr["name"] == k }.try(:merge!, v)
145
+ end
146
+
147
+ ch.update_attribute(:value, child_list.to_json)
148
+ end
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def self.propagate_attr(attr_name, src_list, dest_list)
155
+ for src_field in src_list
156
+ dest_field = dest_list.detect{ |df| df["name"] == src_field["name"] }
157
+ dest_field[attr_name] = src_field[attr_name] if dest_field && src_field[attr_name]
158
+ end
159
+ end
160
+
161
+ # Overwrite pref_to_read, pref_to_write methods, and find_all_for_widget if you want a different way of
162
+ # identifying the proper preference based on your own authorization strategy.
163
+ #
164
+ # The default strategy is:
165
+ # 1) if no masq_user or masq_role defined
166
+ # pref_to_read will search for the preference for user first, then for user's role
167
+ # pref_to_write will always find or create a preference for the current user (never for its role)
168
+ # 2) if masq_user or masq_role is defined
169
+ # pref_to_read and pref_to_write will always take the masquerade into account, e.g. reads/writes will go to
170
+ # the user/role specified
171
+ #
172
+ def self.pref_to_read(name)
173
+ name = name.to_s
174
+ session = Netzke::Base.session
175
+ cond = {:name => name}
176
+
177
+ if session[:masq_user]
178
+ # first, get the prefs for this user it they exist
179
+ res = self.find(:first, :conditions => cond.merge({:user_id => session[:masq_user]}))
180
+ # if it doesn't exist, get them for the user's role
181
+ user = User.find(session[:masq_user])
182
+ res ||= self.find(:first, :conditions => cond.merge({:role_id => user.role.id}))
183
+ # if it doesn't exist either, get them for the World (role_id = 0)
184
+ res ||= self.find(:first, :conditions => cond.merge({:role_id => 0}))
185
+ elsif session[:masq_role]
186
+ # first, get the prefs for this role
187
+ res = self.find(:first, :conditions => cond.merge({:role_id => session[:masq_role]}))
188
+ # if it doesn't exist, get them for the World (role_id = 0)
189
+ res ||= self.find(:first, :conditions => cond.merge({:role_id => 0}))
190
+ elsif session[:masq_world]
191
+ res = self.find(:first, :conditions => cond.merge({:role_id => 0}))
192
+ elsif session[:netzke_user_id]
193
+ user = User.find(session[:netzke_user_id])
194
+ # first, get the prefs for this user
195
+ res = self.find(:first, :conditions => cond.merge({:user_id => user.id}))
196
+ # if it doesn't exist, get them for the user's role
197
+ res ||= self.find(:first, :conditions => cond.merge({:role_id => user.role.id}))
198
+ # if it doesn't exist either, get them for the World (role_id = 0)
199
+ res ||= self.find(:first, :conditions => cond.merge({:role_id => 0}))
200
+ else
201
+ res = self.find(:first, :conditions => cond)
202
+ end
203
+
204
+ res
205
+ end
206
+
207
+ def self.find_or_create_pref_to_read(name)
208
+ name = name.to_s
209
+ attrs = {:name => name}
210
+ extend_attrs_for_current_authority(attrs)
211
+ self.first(:conditions => attrs) || self.new(attrs)
212
+ end
213
+
214
+ def self.extend_attrs_for_current_authority(hsh)
215
+ authority_level, authority_id = Netzke::Base.authority_level
216
+ case authority_level
217
+ when :world
218
+ hsh.merge!(:role_id => 0)
219
+ when :role
220
+ hsh.merge!(:role_id => authority_id)
221
+ when :user
222
+ hsh.merge!(:user_id => authority_id)
223
+ when :self
224
+ hsh.merge!(:user_id => authority_id)
225
+ end
226
+ end
227
+
228
+ def self.pref_to_write(name)
229
+ name = name.to_s
230
+ session = Netzke::Base.session
231
+ cond = {:name => name}
232
+
233
+ if session[:masq_user]
234
+ cond.merge!({:user_id => session[:masq_user]})
235
+ # first, try to find the preference for masq_user
236
+ res = self.find(:first, :conditions => cond)
237
+ # if it doesn't exist, create it
238
+ res ||= self.new(cond)
239
+ elsif session[:masq_role]
240
+ # first, delete all the corresponding preferences for the users that have this role
241
+ Role.find(session[:masq_role]).users.each do |u|
242
+ self.delete_all(cond.merge({:user_id => u.id}))
243
+ end
244
+ cond.merge!({:role_id => session[:masq_role]})
245
+ res = self.find(:first, :conditions => cond)
246
+ res ||= self.new(cond)
247
+ elsif session[:masq_world]
248
+ # first, delete all the corresponding preferences for all users and roles
249
+ self.delete_all(cond)
250
+ # then, create the new preference for the World (role_id = 0)
251
+ res = self.new(cond.merge(:role_id => 0))
252
+ elsif session[:netzke_user_id]
253
+ res = self.find(:first, :conditions => cond.merge({:user_id => session[:netzke_user_id]}))
254
+ res ||= self.new(cond.merge({:user_id => session[:netzke_user_id]}))
255
+ else
256
+ res = self.find(:first, :conditions => cond)
257
+ res ||= self.new(cond)
258
+ end
259
+ res
260
+ end
261
+ end