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.
- data/CHANGELOG.rdoc +17 -0
- data/README.rdoc +1 -1
- data/Rakefile +3 -0
- data/TODO.rdoc +1 -4
- data/features/form_panel.feature +2 -1
- data/features/grid_panel.feature +134 -5
- data/features/nested_attributes.feature +4 -1
- data/features/paging_form_panel.feature +41 -0
- data/features/search_in_grid.feature +20 -7
- data/features/step_definitions/form_panel_steps.rb +35 -0
- data/features/step_definitions/generic_steps.rb +20 -1
- data/features/step_definitions/grid_panel_steps.rb +63 -0
- data/features/support/env.rb +4 -0
- data/features/validations_in_grid.feature +13 -0
- data/features/virtual_attributes.feature +5 -9
- data/javascripts/basepack.js +21 -650
- data/javascripts/datetimefield.js +24 -0
- data/lib/generators/netzke/basepack_generator.rb +10 -0
- data/{generators/netzke_basepack/templates/public_assets → lib/generators/netzke/templates/assets}/ts-checkbox.gif +0 -0
- data/{generators/netzke_basepack → lib/generators/netzke}/templates/create_netzke_field_lists.rb +0 -0
- data/lib/netzke-basepack.rb +3 -41
- data/lib/netzke/active_record/attributes.rb +17 -21
- data/lib/netzke/basepack.rb +6 -1
- data/lib/netzke/basepack/data_accessor.rb +166 -0
- data/lib/netzke/basepack/form_panel.rb +69 -20
- data/lib/netzke/basepack/form_panel/fields.rb +15 -17
- data/lib/netzke/basepack/form_panel/javascripts/comma_list_cbg.js +7 -0
- data/lib/netzke/basepack/form_panel/javascripts/display_mode.js +6 -0
- data/lib/netzke/basepack/form_panel/javascripts/{main.js → form_panel.js} +42 -8
- data/lib/netzke/basepack/form_panel/javascripts/n_radio_group.js +24 -7
- data/lib/netzke/basepack/form_panel/javascripts/readonly_mode.js +52 -0
- data/lib/netzke/basepack/form_panel/services.rb +28 -2
- data/lib/netzke/basepack/form_panel/stylesheets/readonly_mode.css +14 -0
- data/lib/netzke/basepack/grid_panel.rb +151 -181
- data/lib/netzke/basepack/grid_panel/columns.rb +122 -84
- data/lib/netzke/basepack/grid_panel/javascripts/advanced_search.js +22 -27
- data/lib/netzke/basepack/grid_panel/javascripts/{main.js → grid_panel.js} +182 -108
- data/lib/netzke/basepack/grid_panel/record_form_window.rb +7 -2
- data/lib/netzke/basepack/grid_panel/services.rb +58 -39
- data/lib/netzke/basepack/paging_form_panel.rb +86 -25
- data/lib/netzke/basepack/query_builder.rb +105 -0
- data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +140 -0
- data/lib/netzke/basepack/search_panel.rb +61 -44
- data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +153 -0
- data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +64 -0
- data/lib/netzke/basepack/search_window.rb +64 -0
- data/lib/netzke/basepack/simple_app.rb +1 -1
- data/lib/netzke/basepack/simple_app/javascripts/{main.js → simple_app.js} +0 -0
- data/lib/netzke/basepack/tab_panel.rb +1 -2
- data/lib/netzke/basepack/tab_panel/javascripts/{main.js → tab_panel.js} +0 -0
- data/lib/netzke/basepack/version.rb +1 -1
- data/locales/en.yml +71 -4
- data/netzke-basepack.gemspec +47 -22
- data/spec/active_record/attributes_spec.rb +6 -6
- data/spec/components/form_panel_spec.rb +2 -13
- data/stylesheets/datetimefield.css +54 -0
- data/test/rails_app/Gemfile +3 -3
- data/test/rails_app/Gemfile.lock +67 -57
- data/test/rails_app/README +1 -256
- data/test/rails_app/app/components/book_form.rb +1 -3
- data/test/rails_app/app/components/book_form_with_custom_fields.rb +20 -0
- data/test/rails_app/app/components/book_form_with_nested_attributes.rb +18 -0
- data/test/rails_app/app/components/book_grid.rb +3 -1
- data/test/rails_app/app/components/book_grid_loader.rb +24 -0
- data/test/rails_app/app/components/book_grid_with_custom_columns.rb +28 -0
- data/test/rails_app/app/components/book_grid_with_default_values.rb +1 -1
- data/test/rails_app/app/components/book_grid_with_virtual_attributes.rb +0 -1
- data/test/rails_app/app/components/book_paging_form_panel.rb +3 -2
- data/test/rails_app/app/components/book_presentation.rb +3 -3
- data/test/rails_app/app/components/book_query_builder.rb +8 -0
- data/test/rails_app/app/components/book_search_panel.rb +5 -0
- data/test/rails_app/app/components/book_search_panel/javascripts/i18n_de.js +6 -0
- data/test/rails_app/app/components/double_book_grid.rb +18 -0
- data/test/rails_app/app/components/form_without_model.rb +1 -1
- data/test/rails_app/app/components/paging_form_with_search.rb +39 -0
- data/test/rails_app/app/components/user_grid.rb +1 -1
- data/test/rails_app/app/models/author.rb +1 -0
- data/test/rails_app/config/application.rb +6 -1
- data/test/rails_app/config/database.yml +7 -5
- data/test/rails_app/config/environments/test.rb +1 -1
- data/test/rails_app/config/locales/de.yml +35 -0
- data/test/rails_app/config/locales/es.yml +84 -8
- data/test/rails_app/db/migrate/20101026190021_create_books.rb +2 -2
- data/test/rails_app/db/migrate/20110213213050_create_netzke_component_states.rb +20 -0
- data/test/rails_app/db/schema.rb +2 -18
- data/test/rails_app/public/netzke/basepack/ts-checkbox.gif +0 -0
- data/test/unit/active_record_basepack_test.rb +1 -1
- metadata +46 -45
- data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -13
- data/lib/netzke/basepack/grid_panel/search_window.rb +0 -56
- data/lib/netzke/data_accessor.rb +0 -113
- data/lib/netzke/ext.rb +0 -7
- data/lib/netzke/fields_configurator.rb +0 -170
- data/lib/netzke/json_array_editor.rb +0 -67
- data/lib/netzke/masquerade_selector.rb +0 -53
- data/test/rails_app/app/components/some_search_panel.rb +0 -34
- data/test/rails_app/db/development_structure.sql +0 -93
- 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> ":"";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
|
File without changes
|
data/{generators/netzke_basepack → lib/generators/netzke}/templates/create_netzke_field_lists.rb
RENAMED
File without changes
|
data/lib/netzke-basepack.rb
CHANGED
@@ -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 <
|
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
|
-
|
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
|
-
|
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.
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
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}"
|
data/lib/netzke/basepack.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
37
|
-
|
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 :
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
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 :
|
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.
|
62
|
-
:pri
|
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
|