netzke-basepack 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
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