netzke-basepack 0.6.3 → 0.6.4

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 (98) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +3 -0
  4. data/TODO.rdoc +1 -4
  5. data/features/form_panel.feature +2 -1
  6. data/features/grid_panel.feature +134 -5
  7. data/features/nested_attributes.feature +4 -1
  8. data/features/paging_form_panel.feature +41 -0
  9. data/features/search_in_grid.feature +20 -7
  10. data/features/step_definitions/form_panel_steps.rb +35 -0
  11. data/features/step_definitions/generic_steps.rb +20 -1
  12. data/features/step_definitions/grid_panel_steps.rb +63 -0
  13. data/features/support/env.rb +4 -0
  14. data/features/validations_in_grid.feature +13 -0
  15. data/features/virtual_attributes.feature +5 -9
  16. data/javascripts/basepack.js +21 -650
  17. data/javascripts/datetimefield.js +24 -0
  18. data/lib/generators/netzke/basepack_generator.rb +10 -0
  19. data/{generators/netzke_basepack/templates/public_assets → lib/generators/netzke/templates/assets}/ts-checkbox.gif +0 -0
  20. data/{generators/netzke_basepack → lib/generators/netzke}/templates/create_netzke_field_lists.rb +0 -0
  21. data/lib/netzke-basepack.rb +3 -41
  22. data/lib/netzke/active_record/attributes.rb +17 -21
  23. data/lib/netzke/basepack.rb +6 -1
  24. data/lib/netzke/basepack/data_accessor.rb +166 -0
  25. data/lib/netzke/basepack/form_panel.rb +69 -20
  26. data/lib/netzke/basepack/form_panel/fields.rb +15 -17
  27. data/lib/netzke/basepack/form_panel/javascripts/comma_list_cbg.js +7 -0
  28. data/lib/netzke/basepack/form_panel/javascripts/display_mode.js +6 -0
  29. data/lib/netzke/basepack/form_panel/javascripts/{main.js → form_panel.js} +42 -8
  30. data/lib/netzke/basepack/form_panel/javascripts/n_radio_group.js +24 -7
  31. data/lib/netzke/basepack/form_panel/javascripts/readonly_mode.js +52 -0
  32. data/lib/netzke/basepack/form_panel/services.rb +28 -2
  33. data/lib/netzke/basepack/form_panel/stylesheets/readonly_mode.css +14 -0
  34. data/lib/netzke/basepack/grid_panel.rb +151 -181
  35. data/lib/netzke/basepack/grid_panel/columns.rb +122 -84
  36. data/lib/netzke/basepack/grid_panel/javascripts/advanced_search.js +22 -27
  37. data/lib/netzke/basepack/grid_panel/javascripts/{main.js → grid_panel.js} +182 -108
  38. data/lib/netzke/basepack/grid_panel/record_form_window.rb +7 -2
  39. data/lib/netzke/basepack/grid_panel/services.rb +58 -39
  40. data/lib/netzke/basepack/paging_form_panel.rb +86 -25
  41. data/lib/netzke/basepack/query_builder.rb +105 -0
  42. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +140 -0
  43. data/lib/netzke/basepack/search_panel.rb +61 -44
  44. data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +153 -0
  45. data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +64 -0
  46. data/lib/netzke/basepack/search_window.rb +64 -0
  47. data/lib/netzke/basepack/simple_app.rb +1 -1
  48. data/lib/netzke/basepack/simple_app/javascripts/{main.js → simple_app.js} +0 -0
  49. data/lib/netzke/basepack/tab_panel.rb +1 -2
  50. data/lib/netzke/basepack/tab_panel/javascripts/{main.js → tab_panel.js} +0 -0
  51. data/lib/netzke/basepack/version.rb +1 -1
  52. data/locales/en.yml +71 -4
  53. data/netzke-basepack.gemspec +47 -22
  54. data/spec/active_record/attributes_spec.rb +6 -6
  55. data/spec/components/form_panel_spec.rb +2 -13
  56. data/stylesheets/datetimefield.css +54 -0
  57. data/test/rails_app/Gemfile +3 -3
  58. data/test/rails_app/Gemfile.lock +67 -57
  59. data/test/rails_app/README +1 -256
  60. data/test/rails_app/app/components/book_form.rb +1 -3
  61. data/test/rails_app/app/components/book_form_with_custom_fields.rb +20 -0
  62. data/test/rails_app/app/components/book_form_with_nested_attributes.rb +18 -0
  63. data/test/rails_app/app/components/book_grid.rb +3 -1
  64. data/test/rails_app/app/components/book_grid_loader.rb +24 -0
  65. data/test/rails_app/app/components/book_grid_with_custom_columns.rb +28 -0
  66. data/test/rails_app/app/components/book_grid_with_default_values.rb +1 -1
  67. data/test/rails_app/app/components/book_grid_with_virtual_attributes.rb +0 -1
  68. data/test/rails_app/app/components/book_paging_form_panel.rb +3 -2
  69. data/test/rails_app/app/components/book_presentation.rb +3 -3
  70. data/test/rails_app/app/components/book_query_builder.rb +8 -0
  71. data/test/rails_app/app/components/book_search_panel.rb +5 -0
  72. data/test/rails_app/app/components/book_search_panel/javascripts/i18n_de.js +6 -0
  73. data/test/rails_app/app/components/double_book_grid.rb +18 -0
  74. data/test/rails_app/app/components/form_without_model.rb +1 -1
  75. data/test/rails_app/app/components/paging_form_with_search.rb +39 -0
  76. data/test/rails_app/app/components/user_grid.rb +1 -1
  77. data/test/rails_app/app/models/author.rb +1 -0
  78. data/test/rails_app/config/application.rb +6 -1
  79. data/test/rails_app/config/database.yml +7 -5
  80. data/test/rails_app/config/environments/test.rb +1 -1
  81. data/test/rails_app/config/locales/de.yml +35 -0
  82. data/test/rails_app/config/locales/es.yml +84 -8
  83. data/test/rails_app/db/migrate/20101026190021_create_books.rb +2 -2
  84. data/test/rails_app/db/migrate/20110213213050_create_netzke_component_states.rb +20 -0
  85. data/test/rails_app/db/schema.rb +2 -18
  86. data/test/rails_app/public/netzke/basepack/ts-checkbox.gif +0 -0
  87. data/test/unit/active_record_basepack_test.rb +1 -1
  88. metadata +46 -45
  89. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -13
  90. data/lib/netzke/basepack/grid_panel/search_window.rb +0 -56
  91. data/lib/netzke/data_accessor.rb +0 -113
  92. data/lib/netzke/ext.rb +0 -7
  93. data/lib/netzke/fields_configurator.rb +0 -170
  94. data/lib/netzke/json_array_editor.rb +0 -67
  95. data/lib/netzke/masquerade_selector.rb +0 -53
  96. data/test/rails_app/app/components/some_search_panel.rb +0 -34
  97. data/test/rails_app/db/development_structure.sql +0 -93
  98. data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Ext.ux.DateTimePicker & Ext.ux.form.DateTimeField
3
+ * http://www.sencha.com/forum/showthread.php?98292-DateTime-field-again-and-again-)
4
+ * Copyright(c) 2011, Andrew Pleshkov andrew.pleshkov@gmail.com
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ Ext.namespace("Ext.ux");(function(){var a=Ext.ux;a.BaseTimePicker=Ext.extend(Ext.Panel,{timeFormat:"g:i A",header:true,nowText:"Now",doneText:"Done",hourIncrement:1,minIncrement:1,hoursLabel:"Hours",minsLabel:"Minutes",cls:"ux-base-time-picker",width:210,layout:"form",labelAlign:"top",initComponent:function(){this.addEvents("select");this.hourSlider=new Ext.slider.SingleSlider({increment:this.hourIncrement,minValue:0,maxValue:23,fieldLabel:this.hoursLabel,listeners:{change:this._updateTimeValue,scope:this},plugins:new Ext.slider.Tip()});this.minSlider=new Ext.slider.SingleSlider({increment:this.minIncrement,minValue:0,maxValue:59,fieldLabel:this.minsLabel,listeners:{change:this._updateTimeValue,scope:this},plugins:new Ext.slider.Tip()});this.setCurrentTime(false);this.items=[this.hourSlider,this.minSlider];this.bbar=[{text:this.nowText,handler:this.setCurrentTime,scope:this},"->",{text:this.doneText,handler:this.onDone,scope:this}];a.BaseTimePicker.superclass.initComponent.call(this)},setCurrentTime:function(b){this.setValue(new Date(),!!b)},onDone:function(){this.fireEvent("select",this,this.getValue())},setValue:function(c,b){this.hourSlider.setValue(c.getHours(),b);this.minSlider.setValue(c.getMinutes(),b);this._updateTimeValue()},_extractValue:function(){var b=new Date();b.setHours(this.hourSlider.getValue());b.setMinutes(this.minSlider.getValue());return b},getValue:function(){return this._extractValue()},_updateTimeValue:function(){var b=this._extractValue().format(this.timeFormat);if(this.rendered){this.setTitle(b)}},afterRender:function(){a.BaseTimePicker.superclass.afterRender.call(this);this._updateTimeValue()},destroy:function(){this.purgeListeners();this.hourSlider=null;this.minSlider=null;a.BaseTimePicker.superclass.destroy.call(this)}});Ext.reg("basetimepicker",a.BaseTimePicker)})();Ext.namespace("Ext.ux");(function(){var a=Ext.ux;var c="ux-date-time-picker";a.DateTimePicker=Ext.extend(Ext.BoxComponent,{timeLabel:"Time",timeFormat:"g:i A",changeTimeText:"Change...",doneText:"Done",initComponent:function(){a.DateTimePicker.superclass.initComponent.call(this);this.addEvents("select");this.timePickerButton=new Ext.Button({text:this.changeTimeText,handler:this._showTimePicker,scope:this});this._initDatePicker();this.timeValue=new Date();if(this.value){this.setValue(this.value);delete this.value}},_initTimePicker:function(){if(!this.timeMenu){var d=this.initialConfig.timeMenu;if(d&&d.xtype){this.timeMenu=Ext.create(d)}else{var e=Ext.create(Ext.applyIf(this.initialConfig.timePicker||{},{timeFormat:this.timeFormat}),"basetimepicker");this.timeMenu=new b(e,d||{})}if(!Ext.isFunction(this.timeMenu.getPicker)){throw"Your time menu must provide the getPicker() method"}this.timeMenu.on("timeselect",this.onTimeSelect,this)}},_initDatePicker:function(){var e=this.initialConfig.datePicker||{};e.internalRender=this.initialConfig.internalRender;Ext.applyIf(e,{format:this.dateFormat||Ext.DatePicker.prototype.format});var d=this.datePicker=Ext.create(e,"datepicker");d.update=d.update.createSequence(function(){if(this.el!=null&&this.datePicker.rendered){var f=this.datePicker.el.getWidth();this.el.setWidth(f+this.el.getBorderWidth("lr")+this.el.getPadding("lr"))}},this)},_renderDatePicker:function(g){var e=this.datePicker;e.render(g);var j=e.getEl().child(".x-date-bottom");var f=j.getSize(true);var h=["position: absolute","bottom: 0","left: 0","overflow: hidden","width: "+f.width+"px","height: "+f.height+"px"].join(";");var i=g.createChild({tag:"div",cls:"x-date-bottom",style:h,children:[{tag:"table",cellspacing:0,style:"width: 100%",children:[{tag:"tr",children:[{tag:"td",align:"left"},{tag:"td",align:"right"}]}]}]});if(e.showToday){var d={};Ext.each(["text","tooltip","handler","scope"],function(k){d[k]=e.todayBtn.initialConfig[k]});this.todayBtn=new Ext.Button(d).render(i.child("td:first"))}this.doneBtn=new Ext.Button({text:this.doneText,handler:this.onDone,scope:this}).render(i.child("td:last"))},_getFormattedTimeValue:function(d){return d.format(this.timeFormat)},_renderValueField:function(f){var e=c+"-value-ct";var d=!Ext.isEmpty(this.timeLabel)?'<span class="'+e+'-value-label">'+this.timeLabel+":</span>&nbsp;":"";var h=f.insertFirst({tag:"div",cls:[e,"x-date-bottom"].join(" ")});var g=h.createChild({tag:"table",cellspacing:0,style:"width: 100%",children:[{tag:"tr",children:[{tag:"td",align:"left",cls:e+"-value-cell",html:'<div class="'+e+'-value-wrap">'+d+'<span class="'+e+'-value">'+this._getFormattedTimeValue(this.timeValue)+"</span></div>"},{tag:"td",align:"right",cls:e+"-btn-cell"}]}]});this.timeValueEl=g.child("."+e+"-value");this.timeValueEl.on("click",this._showTimePicker,this);this.timePickerButton.render(g.child("td:last"))},onRender:function(e,d){this.el=e.createChild({tag:"div",cls:c,children:[{tag:"div",cls:c+"-inner"}]},d);a.DateTimePicker.superclass.onRender.call(this,e,d);var f=this.el.first();this._renderDatePicker(f);this._renderValueField(f)},_updateTimeValue:function(d){this.timeValue=d;if(this.timeValueEl!=null){this.timeValueEl.update(this._getFormattedTimeValue(d))}},setValue:function(d){this._updateTimeValue(d);this.datePicker.setValue(d.clone())},getValue:function(){var d=this.datePicker.getValue();var e=this.timeValue.getElapsed(this.timeValue.clone().clearTime());return new Date(d.getTime()+e)},onTimeSelect:function(f,d,e){this._updateTimeValue(e)},_showTimePicker:function(){this._initTimePicker();this.timeMenu.getPicker().setValue(this.timeValue,false);if(this.timeMenu.isVisible()){this.timeMenu.hide()}else{this.timeMenu.show(this.timePickerButton.el,null,this.parentMenu)}},onDone:function(){this.fireEvent("select",this,this.getValue())},destroy:function(){Ext.destroy(this.timePickerButton);this.timePickerButton=null;if(this.timeValueEl){this.timeValueEl.remove();this.timeValueEl=null}Ext.destroy(this.datePicker);this.datePicker=null;if(this.timeMenu){Ext.destroy(this.timeMenu);this.timeMenu=null}if(this.todayBtn){Ext.destroy(this.todayBtn);this.todayBtn=null}if(this.doneBtn){Ext.destroy(this.doneBtn);this.doneBtn=null}this.parentMenu=null;a.DateTimePicker.superclass.destroy.call(this)}});Ext.reg("datetimepicker",a.DateTimePicker);var b=a.DateTimePicker.Menu=Ext.extend(Ext.menu.Menu,{enableScrolling:false,hideOnClick:false,plain:true,showSeparator:false,constructor:function(e,d){d=d||{};if(d.picker){delete d.picker}this.picker=Ext.create(e);b.superclass.constructor.call(this,Ext.applyIf({items:this.picker},d));this.addEvents("timeselect");this.picker.on("select",this.onTimeSelect,this)},getPicker:function(){return this.picker},onTimeSelect:function(d,e){this.hide();this.fireEvent("timeselect",this,d,e)},destroy:function(){this.purgeListeners();this.picker=null;b.superclass.destroy.call(this)}})})();Ext.namespace("Ext.ux.form");(function(){var c=Ext.ux.form;var a=Ext.isIE7&&Ext.isStrict;var b=Ext.extend(Ext.menu.Menu,{enableScrolling:false,plain:true,showSeparator:false,hideOnClick:true,pickerId:null,cls:"x-date-menu x-date-time-menu",constructor:function(e,d){b.superclass.constructor.call(this,Ext.applyIf({items:e},d||{}));this.primaryPicker=e;e.parentMenu=this;this.on("beforeshow",this.onBeforeShow,this);this.strict=a;if(this.strict){this.on("show",this.onShow,this,{single:true,delay:20})}this.picker=e.datePicker;this.relayEvents(e,["select"]);this.on("show",e.focus,e);this.on("select",this.menuHide,this);if(this.handler){this.on("select",this.handler,this.scope||this)}},menuHide:function(){if(this.hideOnClick){this.hide(true)}},onBeforeShow:function(){if(this.picker){this.picker.hideMonthPicker(true)}},onShow:function(){var d=this.picker.getEl();d.setWidth(d.getWidth())},destroy:function(){this.primaryPicker=null;this.picker=null;b.superclass.destroy.call(this)}});c.DateTimeField=Ext.extend(Ext.form.DateField,{timeFormat:"g:i A",defaultAutoCreate:{tag:"input",type:"text",size:"22",autocomplete:"off"},initComponent:function(){c.DateTimeField.superclass.initComponent.call(this);this.dateFormat=this.dateFormat||this.format;var d=this._createPicker();this.format=this.dateFormat+" "+this.timeFormat;this.menu=new b(d,{hideOnClick:false})},_createPicker:function(){var d=this.initialConfig.picker||{};Ext.apply(d,{ctCls:"x-menu-date-item",internalRender:a||!Ext.isIE});Ext.applyIf(d,{dateFormat:this.dateFormat,timeFormat:this.timeFormat});return Ext.create(d,"datetimepicker")},onTriggerClick:function(){c.DateTimeField.superclass.onTriggerClick.apply(this,arguments);this.menu.primaryPicker.setValue(this.getValue()||new Date())}});Ext.reg("datetimefield",c.DateTimeField)})();
@@ -0,0 +1,10 @@
1
+ module Netzke
2
+ class BasepackGenerator < ::Rails::Generators::Base
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ desc 'Copies necessary assets to public/netzke/basepack'
6
+ def execute
7
+ copy_file 'assets/ts-checkbox.gif', "public/netzke/basepack/ts-checkbox.gif"
8
+ end
9
+ end
10
+ end
@@ -2,56 +2,18 @@
2
2
  require 'netzke-core'
3
3
  require 'active_support/dependencies'
4
4
 
5
- # path = File.dirname(__FILE__)
6
- # $LOAD_PATH << path
7
-
8
5
  # Make components auto-loadable
9
6
  ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
10
7
 
11
8
  require 'netzke/basepack'
12
9
 
13
10
  module Netzke
14
- autoload :Ext, 'ext'
15
-
16
11
  module Basepack
17
- class Engine < ::Rails::Engine
18
- config.before_initialize do
19
- I18n.load_path << File.dirname(__FILE__) + '/../locales/en.yml'
20
- end
12
+ class Engine < Rails::Engine
21
13
  end
22
14
  end
23
-
24
15
  end
25
16
 
26
- Netzke::Basepack.init
27
-
28
- # Netzke::Core.javascripts << "#{File.dirname(__FILE__)}/../../javascripts/basepack.js"
29
- # Netzke::Core.stylesheets << "#{File.dirname(__FILE__)}/../../stylesheets/basepack.css"
30
-
31
- # Make this plugin auto-reloadable for easier development
32
- # ActiveSupport::Dependencies.autoload_once_paths.delete(path)
33
-
34
- # Make gem's models auto-loadable
35
- # %w{ models }.each do |dir|
36
- # path = File.join(File.dirname(__FILE__), 'app', dir)
37
- # $LOAD_PATH << path
38
- # ActiveSupport::Dependencies.autoload_paths << path
39
- # ActiveSupport::Dependencies.autoload_once_paths.delete(path)
40
- # end
41
-
42
- # Include javascript & styles required by all basepack components.
43
- # These files will get loaded at the initial load of the framework (along with Ext and Netzke-core).
44
-
45
-
46
- # FIXME: The following stylesheet inclusion doesn't *really* belong here, being component-specific,
47
- # but I don't see any other solution for now. The problem is that these stylesheets come straight from
48
- # Ext JS, having *relative* URLs to the images, which doesn't allow us to include them all together as those stylesheets
49
- # from Netzke.
50
-
51
- # Used by FormPanel (file upload field)
52
- # Netzke::Base.config[:external_css] << "/extjs/examples/ux/fileuploadfield/css/fileuploadfield"
53
-
54
- # Used by GridPanel
55
- # Netzke::Base.config[:external_css] << "/extjs/examples/ux/gridfilters/css/RangeMenu"
56
- # Netzke::Base.config[:external_css] << "/extjs/examples/ux/gridfilters/css/GridFilters"
17
+ I18n.load_path << File.dirname(__FILE__) + '/../locales/en.yml'
57
18
 
19
+ Netzke::Basepack.init
@@ -138,7 +138,7 @@ module Netzke
138
138
  res = []
139
139
  for a in attributes
140
140
  next if a[:included] == false
141
- res << value_for_attribute(a)
141
+ res << value_for_attribute(a, a[:nested_attribute])
142
142
  end
143
143
  res
144
144
  end
@@ -148,26 +148,33 @@ module Netzke
148
148
  res = {}
149
149
  for a in (attributes.is_a?(Hash) ? attributes.values : attributes)
150
150
  next if a[:included] == false
151
- res[a[:name].to_sym] = self.value_for_attribute(a)
151
+ res[a[:name].to_sym] = self.value_for_attribute(a, a[:nested_attribute])
152
152
  end
153
153
  res
154
154
  end
155
155
 
156
156
  # Fetches the value specified by an (association) attribute
157
- def value_for_attribute(a)
157
+ # If +through_association+ is true, get the value of the association by provided method, *not* the associated record's id
158
+ # E.g., author__name with through_association set to true may return "Vladimir Nabokov", while with through_association set to false, it'll return author_id for the current record
159
+ def value_for_attribute(a, through_association = false)
158
160
  v = if a[:getter]
159
161
  a[:getter].call(self)
160
162
  elsif respond_to?("#{a[:name]}")
161
163
  send("#{a[:name]}")
162
164
  elsif is_association_attr?(a)
163
165
  split = a[:name].to_s.split(/\.|__/)
164
- split.inject(self) do |r,m|
165
- if r.respond_to?(m)
166
- r.send(m)# unless res.nil?
167
- else
168
- logger.debug "!!! Netzke::Basepack: Wrong attribute name: #{a[:name]}" unless r.nil?
169
- nil
166
+ assoc = self.class.reflect_on_association(split.first.to_sym)
167
+ if through_association
168
+ split.inject(self) do |r,m| # TODO: do we really need to descend deeper than 1 level?
169
+ if r.respond_to?(m)
170
+ r.send(m)
171
+ else
172
+ logger.debug "!!! Netzke::Basepack: Wrong attribute name: #{a[:name]}" unless r.nil?
173
+ nil
174
+ end
170
175
  end
176
+ else
177
+ self.send("#{assoc.options[:foreign_key] || assoc.name.to_s.foreign_key}")
171
178
  end
172
179
  end
173
180
 
@@ -205,18 +212,7 @@ module Netzke
205
212
  # what should we do in this case?
206
213
  end
207
214
  else
208
- begin
209
- assoc_instance = assoc.klass.send("find_by_#{assoc_method}", v)
210
- rescue NoMethodError
211
- assoc_instance = nil
212
- logger.debug "!!! Netzke::Basepack: No find_by_#{assoc_method} method for class #{assoc.klass.name}\n"
213
- end
214
- if (assoc_instance)
215
- # we found association instance to assign
216
- self.send("#{split.first}=", assoc_instance)
217
- else
218
- logger.debug "!!! Netzke::Basepack: Couldn't find association #{split.first} by #{assoc_method} '#{v}'"
219
- end
215
+ self.send("#{assoc.options[:foreign_key] || assoc.name.to_s.foreign_key}=", v)
220
216
  end
221
217
  else
222
218
  logger.debug "!!! Netzke::Basepack: Association #{assoc} is not known for class #{self.class.name}"
@@ -8,9 +8,13 @@ module Netzke
8
8
  mattr_accessor :icons_uri
9
9
 
10
10
  class << self
11
+
11
12
  # Called from netzke-basepack.rb
12
13
  def init
14
+ Netzke::Core.ext_javascripts << "#{File.dirname(__FILE__)}/../../javascripts/datetimefield.js"
13
15
  Netzke::Core.ext_javascripts << "#{File.dirname(__FILE__)}/../../javascripts/basepack.js"
16
+
17
+ Netzke::Core.ext_stylesheets << "#{File.dirname(__FILE__)}/../../stylesheets/datetimefield.css"
14
18
  Netzke::Core.ext_stylesheets << "#{File.dirname(__FILE__)}/../../stylesheets/basepack.css"
15
19
 
16
20
  Netzke::Core.external_ext_css << "/extjs/examples/ux/gridfilters/css/RangeMenu"
@@ -26,5 +30,6 @@ module Netzke
26
30
  yield self
27
31
  end
28
32
  end
33
+
29
34
  end
30
- end
35
+ end
@@ -0,0 +1,166 @@
1
+ require 'netzke/active_record'
2
+
3
+ module Netzke
4
+ module Basepack
5
+ # This module is included into such data-driven components as GridPanel, FormPanel, PagingFormPanel, etc.
6
+ module DataAccessor
7
+
8
+ # Returns options for comboboxes in grids/forms
9
+ def combobox_options_for_column(column, method_options = {})
10
+ query = method_options[:query]
11
+
12
+ # First, check if we have options for this column defined in persistent storage
13
+ options = column[:combobox_options] && column[:combobox_options].split("\n")
14
+ if options
15
+ query ? options.select{ |o| o.index(/^#{query}/) }.map{ |el| [el] } : options
16
+ else
17
+ assoc, assoc_method = assoc_and_assoc_method_for_column(column)
18
+
19
+ if assoc
20
+ # Options for an asssociation attribute
21
+
22
+ relation = assoc.klass.scoped
23
+
24
+ relation = relation.extend_with(method_options[:scope]) if method_options[:scope]
25
+
26
+ if assoc.klass.column_names.include?(assoc_method)
27
+ # apply query
28
+ relation = relation.where(:"#{assoc_method}".like => "#{query}%") if query.present?
29
+ relation.all.map{ |r| [r.id, r.send(assoc_method)] }
30
+ else
31
+ relation.all.map{ |r| [r.id, r.send(assoc_method)] }.select{ |id,value| value =~ /^#{query}/ }
32
+ end
33
+
34
+ else
35
+ # Options for a non-association attribute
36
+ res=data_class.netzke_combo_options_for(column[:name], method_options)
37
+
38
+ # ensure it is an array-in-array, as Ext will fail otherwise
39
+ raise RuntimeError, "netzke_combo_options_for should return an Array" unless res.kind_of? Array
40
+ return [[]] if res.empty?
41
+
42
+ unless res.first.kind_of? Array
43
+ res=res.map do |v|
44
+ [v]
45
+ end
46
+ end
47
+ return res
48
+
49
+
50
+ end
51
+ end
52
+ end
53
+
54
+ # Normalize array of attributes
55
+ # [:col1, "col2", {:name => :col3}] =>
56
+ # [{:name => "col1"}, {:name => "col2"}, {:name => "col3"}]
57
+ def normalize_attrs(attrs)
58
+ attrs.map{ |a| normalize_attr(a) }
59
+ end
60
+
61
+ # Normalize an attribute, e.g.:
62
+ # :first_name =>
63
+ # {:name => "first_name"}
64
+ def normalize_attr(a)
65
+ a.is_a?(Symbol) || a.is_a?(String) ? {:name => a.to_s} : a.merge(:name => a[:name].to_s)
66
+ end
67
+
68
+ # Returns association and association method for a column
69
+ def assoc_and_assoc_method_for_column(c)
70
+ assoc_name, assoc_method = c[:name].split('__')
71
+ assoc = data_class.reflect_on_association(assoc_name.to_sym) if assoc_method
72
+ [assoc, assoc_method]
73
+ end
74
+
75
+ def association_attr?(name)
76
+ !!name.to_s.index("__")
77
+ end
78
+
79
+ # Model class
80
+ def data_class
81
+ @data_class ||= begin
82
+ klass = constantize_class_name("Netzke::ModelExtensions::#{config[:model]}For#{short_component_class_name}") || original_data_class
83
+ end
84
+ end
85
+
86
+ # Model class before model extensions are taken into account
87
+ def original_data_class
88
+ @original_data_class ||= begin
89
+ ::ActiveSupport::Deprecation.warn("data_class_name option is deprecated. Use model instead", caller) if config[:data_class_name]
90
+ model_name = config[:model] || config[:data_class_name]
91
+ model_name && constantize_class_name(model_name)
92
+ end
93
+ end
94
+
95
+ # whether a column is bound to the primary_key
96
+ def primary_key_attr?(a)
97
+ data_class && a[:name].to_s == data_class.primary_key
98
+ end
99
+
100
+ # Mark an attribute as "virtual" by default, when it doesn't reflect a model column, or a model column of an association
101
+ def set_default_virtual(c)
102
+ if c[:virtual].nil? # sometimes at maybe handy to mark a column as non-virtual forcefully
103
+ assoc, assoc_method = assoc_and_assoc_method_for_column(c)
104
+ if assoc
105
+ c[:virtual] = true if !assoc.klass.column_names.map(&:to_sym).include?(assoc_method.to_sym)
106
+ else
107
+ c[:virtual] = true if !data_class.column_names.map(&:to_sym).include?(c[:name].to_sym)
108
+ end
109
+ end
110
+ end
111
+
112
+ # An ActiveRecord::Relation instance encapsulating all the necessary conditions.
113
+ # Using the meta_where magic.
114
+ def get_relation(params = {})
115
+ # make params coming from Ext grid filters understandable by meta_where
116
+ conditions = params[:filter] && convert_filters(params[:filter]) || {}
117
+
118
+ relation = data_class.where(conditions)
119
+
120
+ if params[:extra_conditions]
121
+ extra_conditions = normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
122
+ relation = relation.extend_with_netzke_conditions(extra_conditions) if params[:extra_conditions]
123
+ end
124
+
125
+ if params[:query]
126
+ # array of arrays of conditions that should be joined by OR
127
+ query = ActiveSupport::JSON.decode(params[:query])
128
+ meta_where = query.map do |conditions|
129
+ normalize_and_conditions(conditions)
130
+ end
131
+
132
+ # join them by OR
133
+ meta_where = meta_where.inject(meta_where.first){ |r,c| r | c } if meta_where.present?
134
+ end
135
+
136
+ relation = relation.where(meta_where)
137
+
138
+ relation = relation.extend_with(config[:scope]) if config[:scope]
139
+
140
+ relation
141
+ end
142
+
143
+ protected
144
+
145
+ def normalize_and_conditions(conditions)
146
+ and_conditions = conditions.map do |q|
147
+ value = q["value"]
148
+ case q["operator"]
149
+ when "contains"
150
+ q["attr"].to_sym.matches % %Q{%#{value}%}
151
+ else
152
+ if value == false || value == true
153
+ q["attr"].to_sym.eq % (value ? 1 : 0)
154
+ else
155
+ q["attr"].to_sym.send(q["operator"]) % value
156
+ end
157
+ end
158
+ end
159
+
160
+ # join them by AND
161
+ and_conditions.inject(and_conditions.first){ |r,c| r & c } if and_conditions.present?
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -1,6 +1,5 @@
1
1
  require "netzke/basepack/form_panel/fields"
2
2
  require "netzke/basepack/form_panel/services"
3
- require "netzke/data_accessor"
4
3
  # require "netzke/plugins/configuration_tool"
5
4
 
6
5
  module Netzke
@@ -20,6 +19,8 @@ module Netzke
20
19
  # The layout of the form is configured by supplying the +item+ config option, same way it would be configured in Ext (thus allowing for complex form layouts). FormPanel will expand fields by looking at their names (unless +no_binding+ set to +true+ is specified for a specific field).
21
20
  class FormPanel < Netzke::Base
22
21
 
22
+ js_base_class "Ext.form.FormPanel"
23
+
23
24
  # Class-level configuration
24
25
  class_attribute :config_tool_available
25
26
  self.config_tool_available = true
@@ -29,38 +30,67 @@ module Netzke
29
30
 
30
31
  include self::Services
31
32
  include self::Fields
32
- include Netzke::DataAccessor
33
-
34
- js_base_class "Ext.form.FormPanel"
33
+ include Netzke::Basepack::DataAccessor
34
+
35
+ action :apply do
36
+ {
37
+ :text => I18n.t('netzke.basepack.form_panel.actions.apply'),
38
+ :tooltip => I18n.t('netzke.basepack.form_panel.actions.apply_tooltip'),
39
+ :icon => :tick
40
+ }
41
+ end
35
42
 
36
- def bbar(config)
37
- config[:mode] == :lockable ? nil : [:apply.action]
43
+ action :edit do
44
+ {
45
+ :text => I18n.t('netzke.basepack.form_panel.actions.edit'),
46
+ :tooltip => I18n.t('netzke.basepack.form_panel.actions.edit_tooltip'),
47
+ :icon => :pencil
48
+ }
38
49
  end
39
50
 
40
- action :apply, :text => I18n.t('netzke.basepack.form_panel.apply', :default => "Apply"), :icon => :tick
41
- action :edit, :text => I18n.t('netzke.basepack.form_panel.edit', :default => "Edit"), :icon => :pencil
42
- action :cancel, :text => I18n.t('netzke.basepack.form_panel.cancel', :default => "Cancel"), :icon => :cancel
51
+ action :cancel do
52
+ {
53
+ :text => I18n.t('netzke.basepack.form_panel.actions.cancel'),
54
+ :tooltip => I18n.t('netzke.basepack.form_panel.actions.cancel_tooltip'),
55
+ :icon => :cancel
56
+ }
57
+ end
43
58
 
44
59
  def configuration
45
- sup = super
60
+ super.tap do |sup|
61
+ configure_locked(sup)
62
+ configure_bbar(sup)
63
+ end
64
+ end
65
+
66
+ def configure_locked(c)
67
+ c[:locked] = c[:locked].nil? ? (c[:mode] == :lockable) : c[:locked]
68
+ end
46
69
 
47
- sup.merge(
48
- :bbar => sup[:bbar] || bbar(sup),
49
- :locked => sup[:locked].nil? ? (sup[:mode] == :lockable) : sup[:locked]
50
- )
70
+ def configure_bbar(c)
71
+ c[:bbar] = [:apply.action] if c[:bbar].nil?
51
72
  end
52
73
 
53
74
  # Extra javascripts
54
- js_mixin :main
75
+ js_mixin :form_panel
55
76
  js_include :comma_list_cbg
56
- js_include :n_radio_group, :display_mode
77
+ js_include :n_radio_group, :display_mode, :readonly_mode
57
78
  # Netzke::Base.config[:ext_location] + "/examples/ux/fileuploadfield/FileUploadField.js",
58
79
  # "#{File.dirname(__FILE__)}/form_panel/javascripts/netzkefileupload.js"
59
80
 
81
+ # Extra CSS
82
+ css_include :readonly_mode
83
+
60
84
  def js_config
61
- super.merge(
62
- :pri => data_class && data_class.primary_key
63
- )
85
+ super.tap do |res|
86
+ res[:pri] = data_class && data_class.primary_key
87
+ res[:record] = js_record_data if record
88
+ end
89
+ end
90
+
91
+ # A hash of record data including the meta field
92
+ def js_record_data
93
+ record.to_hash(fields).merge(:_meta => meta_field).literalize_keys
64
94
  end
65
95
 
66
96
  def record
@@ -101,7 +131,26 @@ module Netzke
101
131
  private
102
132
 
103
133
  def self.server_side_config_options
104
- super + [:record]
134
+ super + [:record, :scope]
135
+ end
136
+
137
+ def meta_field
138
+ {}.tap do |res|
139
+ assoc_values = get_association_values
140
+ res[:association_values] = assoc_values.literalize_keys if record && !assoc_values.empty?
141
+ end
142
+ end
143
+
144
+ def get_association_values
145
+ fields_that_need_associated_values = fields.select{ |k,v| k.to_s.index("__") && !fields[k][:nested_attribute] }
146
+ # Take care of Ruby 1.8.7
147
+ if fields_that_need_associated_values.is_a?(Array)
148
+ fields_that_need_associated_values = fields_that_need_associated_values.inject({}){|r,(k,v)| r.merge(k => v)}
149
+ end
150
+
151
+ fields_that_need_associated_values.each_pair.inject({}) do |r,(k,v)|
152
+ r.merge(k => record.value_for_attribute(fields_that_need_associated_values[k], true))
153
+ end
105
154
  end
106
155
 
107
156
  # include ::Netzke::Plugins::ConfigurationTool if config_tool_available # it will load ConfigurationPanel into a modal window