netzke-basepack 0.5.8 → 0.5.9

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.
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