netzke-basepack 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG.rdoc +22 -1
  2. data/Rakefile +1 -1
  3. data/lib/netzke/active_record/attributes.rb +1 -1
  4. data/lib/netzke/basepack/action_column.rb +70 -0
  5. data/lib/netzke/basepack/action_column/javascripts/action_column.js +61 -0
  6. data/lib/netzke/basepack/data_accessor.rb +12 -17
  7. data/lib/netzke/basepack/form_panel.rb +5 -4
  8. data/lib/netzke/basepack/form_panel/fields.rb +1 -1
  9. data/lib/netzke/basepack/form_panel/javascripts/form_panel.js +3 -3
  10. data/lib/netzke/basepack/grid_panel.rb +81 -30
  11. data/lib/netzke/basepack/grid_panel/columns.rb +20 -9
  12. data/lib/netzke/basepack/grid_panel/javascripts/event_handling.js +0 -1
  13. data/lib/netzke/basepack/grid_panel/javascripts/grid_panel.js +1 -1
  14. data/lib/netzke/basepack/grid_panel/record_form_window.rb +5 -5
  15. data/lib/netzke/basepack/grid_panel/services.rb +0 -1
  16. data/lib/netzke/basepack/version.rb +1 -1
  17. data/netzke-basepack.gemspec +11 -6
  18. data/test/basepack_test_app/Gemfile.lock +1 -1
  19. data/test/basepack_test_app/app/components/author_grid.rb +1 -5
  20. data/test/basepack_test_app/app/components/book_form.rb +25 -24
  21. data/test/basepack_test_app/app/components/book_grid.rb +6 -9
  22. data/test/basepack_test_app/app/components/book_grid_with_column_actions.rb +15 -0
  23. data/test/basepack_test_app/app/components/book_grid_with_custom_columns.rb +16 -23
  24. data/test/basepack_test_app/app/components/book_grid_with_default_values.rb +5 -7
  25. data/test/basepack_test_app/app/components/book_grid_with_extra_feedback.rb +2 -5
  26. data/test/basepack_test_app/app/components/book_grid_with_overridden_columns.rb +15 -0
  27. data/test/basepack_test_app/app/components/book_grid_with_persistence.rb +0 -1
  28. data/test/basepack_test_app/app/components/book_paging_form_panel.rb +2 -2
  29. data/test/basepack_test_app/app/components/user_form.rb +16 -19
  30. data/test/basepack_test_app/app/components/user_grid.rb +6 -8
  31. data/test/basepack_test_app/db/migrate/20110909071740_add_published_on_to_books.rb +5 -0
  32. data/test/basepack_test_app/db/schema.rb +2 -1
  33. data/test/basepack_test_app/features/form_panel.feature +11 -0
  34. data/test/basepack_test_app/features/grid_panel.feature +34 -33
  35. data/test/basepack_test_app/features/grid_panel_filters.feature +61 -0
  36. data/test/basepack_test_app/features/paging_form_panel.feature +7 -30
  37. data/test/basepack_test_app/features/step_definitions/grid_panel_steps.rb +28 -0
  38. metadata +15 -10
  39. data/lib/netzke/basepack/grid_panel/multi_edit_form.rb +0 -16
data/CHANGELOG.rdoc CHANGED
@@ -1,6 +1,27 @@
1
+ = 0.7.2 - 2011-10-20
2
+ * bug fix
3
+ * Filter on a date column
4
+ * Using date column caused FormPanel to crash
5
+ * Virtual columns are no longer editable by default
6
+
7
+ * improvements
8
+ * New DSL method (model) for declaring a model for grids
9
+ * New DSL method (column) for declaring columns for grids
10
+ * New :override_columns config option for grid allows overriding specified column config without influencing other columns' order/presence
11
+ * New DSL method (override_column) to override a specific column config without influencing other columns' order/presence.
12
+ * Grid's title is set to model's pluralized name by default.
13
+ * New :read_only option for FormPanel - makes all fields read-only and removes the "Apply" button.
14
+ * FormPanel implements DSL shortcuts for the following options in default config: :model, :items, :record_id.
15
+ * GridPanel implements DSL shortcuts for the following options in default config: :model, :add_form_config, :edit_form_config, :multi_edit_form_config.
16
+ * New FormPanel option :multi_edit - set when the form is used for editing multiple records at a time
17
+ * Introduce action columns (see BookGridWithColumnActions)
18
+
19
+ * API changes
20
+ * Skip supporting ModelExtensions. The preferred way is using setters and getters on columns/fields.
21
+
1
22
  = 0.7.1 - 2011-09-04
2
23
  * bug fix
3
- * fixing the netzke_load endpoint when association fields are present
24
+ * FormPanel: fix the netzke_load endpoint when association fields are present
4
25
  * dates were not displayed in date fields, and submitting a form with date fields might result in erasing those fields
5
26
 
6
27
  * Rails 3.1 compatibility
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gemspec.email = "sergei@playcode.nl"
10
10
  gemspec.homepage = "http://netzke.org"
11
11
  gemspec.authors = ["Sergei Kozlov"]
12
- gemspec.add_dependency("netzke-core", "~>0.7.0")
12
+ gemspec.add_dependency("netzke-core", "~>0.7.4")
13
13
  gemspec.add_dependency("will_paginate", "~>3.0.0")
14
14
  gemspec.add_dependency("acts_as_list", "~>0.1.4")
15
15
  gemspec.post_install_message = <<-MESSAGE
@@ -184,7 +184,7 @@ module Netzke
184
184
  end
185
185
 
186
186
  # a work-around for to_json not taking the current timezone into account when serializing ActiveSupport::TimeWithZone
187
- v = v.to_datetime.to_s(:db) if v.is_a?(ActiveSupport::TimeWithZone)
187
+ v = v.to_datetime.to_s(:db) if [ActiveSupport::TimeWithZone, Date].include?(v.class)
188
188
 
189
189
  v
190
190
  end
@@ -0,0 +1,70 @@
1
+ module Netzke
2
+ module Basepack
3
+ module ActionColumn
4
+ extend ActiveSupport::Concern
5
+
6
+ included do |base|
7
+ js_include :action_column
8
+ end
9
+
10
+ module ClassMethods
11
+ # Register an action
12
+ def register_column_action(name)
13
+ current_column_actions = read_inheritable_attribute(:column_actions) || []
14
+ current_column_actions << name
15
+ write_inheritable_attribute(:column_actions, current_column_actions.uniq)
16
+ end
17
+
18
+ # Returns registered actions
19
+ def registered_column_actions
20
+ read_inheritable_attribute(:column_actions) || []
21
+ end
22
+
23
+ # Use this method to define column actions in your component, e.g.:
24
+ #
25
+ # column_action :edit, :icon => "/images/icons/edit.png"
26
+ #
27
+ # TODO: List all options.
28
+ # TODO: think how it'll be possible to override individual column_actions (if need to bother at all)
29
+ def column_action(name, params = {})
30
+ params[:name] = name
31
+ params[:column] ||= "actions"
32
+ params[:icon] ||= "/extjs/examples/shared/icons/fam/cog.png"
33
+ params[:tooltip] = params[:tooltip].presence || name.to_s.humanize
34
+ params[:handler] ||= "on_#{name}"
35
+ register_column_action(name);
36
+ define_method "#{name}_column_action" do |record=nil| # TODO: this won't work in Ruby 1.8.7
37
+ params[:row_config] && record ? params.merge(params[:row_config].call(record, self)) : params
38
+ end
39
+ end
40
+ end
41
+
42
+ def initial_columns(with_excluded = false)
43
+ orig_columns = super
44
+
45
+ action_column_names = column_actions.map{ |action| action[:column] }.uniq
46
+ action_columns = orig_columns.select{ |c| action_column_names.include? c[:name] }
47
+
48
+ # Append the column if none found AND no explicit column configuration was provided
49
+ if action_columns.empty? && !config[:columns]
50
+ action_columns = [{:name => "actions"}.merge(config[:override_columns].try(:fetch, :actions, nil) || {})]
51
+ orig_columns += action_columns
52
+ end
53
+
54
+ action_columns.each do |c|
55
+ c[:xtype] = :netzkeactioncolumn
56
+ c[:getter] = lambda do |r|
57
+ self.class.registered_column_actions.select{ |action_name| self.send("#{action_name}_column_action")[:column] == c[:name] }.map{ |action_name| self.send("#{action_name}_column_action", r) }.to_nifty_json
58
+ end
59
+ end
60
+
61
+ orig_columns
62
+ end
63
+
64
+ def column_actions
65
+ self.class.registered_column_actions.map{ |action_name| self.send("#{action_name}_column_action")}
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,61 @@
1
+ Ext.define('Ext.grid.column.NetzkeAction', {
2
+ extend: 'Ext.grid.column.Action',
3
+ alias: ['widget.netzkeactioncolumn'],
4
+
5
+ constructor: function(config) {
6
+ var me = this,
7
+ cfg = Ext.apply({}, config),
8
+ i,
9
+ item;
10
+
11
+ me.callParent([cfg]);
12
+
13
+ me.renderer = function(actions, meta) {
14
+ // previous renderer
15
+ var v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '',
16
+ actions = Ext.decode(actions),
17
+ l = actions.length,
18
+ action;
19
+
20
+ meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
21
+
22
+ for (i = 0; i < l; i++) {
23
+ action = actions[i];
24
+ if (!action.hidden) {
25
+ v += '<img alt="' + (action.altText || me.altText) + '" src="' + (action.icon || Ext.BLANK_IMAGE_URL) +
26
+ '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (action.iconCls || '') +
27
+ ' ' + (Ext.isFunction(action.getClass) ? action.getClass.apply(action.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
28
+ ((action.tooltip) ? ' data-qtip="' + action.tooltip + '"' : '') +
29
+ ' data-name="' + action.name + '"' +
30
+ ((action.handler) ? ' data-handler="' + action.handler + '"' : '') + ' />';
31
+ }
32
+ }
33
+
34
+ return v;
35
+ };
36
+ },
37
+
38
+ processEvent : function(type, view, cell, recordIndex, cellIndex, e){
39
+ var me = this,
40
+ target = e.getTarget(),
41
+ match = target.className.match("x-action-col-icon"),
42
+ fn, grid, record;
43
+ if (match) {
44
+ if (type == 'click') {
45
+ grid = me.ownerCt.ownerCt;
46
+ fn = (target.getAttribute("data-handler") || "").camelize(true);
47
+ fn = Ext.isFunction(grid[fn]) ? grid[fn] : undefined;
48
+ // if (fn) fn.call(grid, view, recordIndex, cellIndex, target, e);
49
+ if (fn) {
50
+ record = grid.getStore().getAt(recordIndex);
51
+ fn.call(grid, record, target, e);
52
+ } else {
53
+ Netzke.warning("Undefined handler for column action '" + target.getAttribute("data-name") + "'");
54
+ }
55
+ } else if (type == 'mousedown') {
56
+ return false;
57
+ }
58
+ }
59
+ return me.callParent(arguments);
60
+ }
61
+ });
@@ -76,18 +76,7 @@ module Netzke
76
76
 
77
77
  # Model class
78
78
  def data_class
79
- @data_class ||= begin
80
- klass = constantize_class_name("Netzke::ModelExtensions::#{config[:model]}For#{short_component_class_name}") || original_data_class
81
- end
82
- end
83
-
84
- # Model class before model extensions are taken into account
85
- def original_data_class
86
- @original_data_class ||= begin
87
- ::ActiveSupport::Deprecation.warn("data_class_name option is deprecated. Use model instead", caller) if config[:data_class_name]
88
- model_name = config[:model] || config[:data_class_name]
89
- model_name && constantize_class_name(model_name)
90
- end
79
+ @data_class ||= config[:model] && config[:model].constantize
91
80
  end
92
81
 
93
82
  # whether a column is bound to the primary_key
@@ -164,7 +153,9 @@ module Netzke
164
153
  # relation.where(["id > ?", 10]).where(["food_name like ?", "%pizza%"])
165
154
  def apply_column_filters(relation, column_filter)
166
155
  res = relation
167
- operator_map = {"lt" => "<", "gt" => ">"}
156
+ operator_map = {"lt" => "<", "gt" => ">", "eq" => "="}
157
+
158
+ table_name = data_class.table_name
168
159
 
169
160
  # these are still JSON-encoded due to the migration to Ext.direct
170
161
  column_filter=JSON.parse(column_filter)
@@ -183,11 +174,15 @@ module Netzke
183
174
 
184
175
  case v["type"]
185
176
  when "string"
186
- res = res.where(["#{field} like ?", "%#{value}%"])
187
- when "numeric", "date"
188
- res = res.where(["#{field} #{op} ?", value])
177
+ res = res.where(["#{table_name}.#{field} like ?", "%#{value}%"])
178
+ when "date"
179
+ # convert value to the DB date
180
+ value.match /(\d\d)\/(\d\d)\/(\d\d\d\d)/
181
+ res = res.where("#{table_name}.#{field} #{op} ?", "#{$3}-#{$1}-#{$2}")
182
+ when "numeric"
183
+ res = res.where(["#{table_name}.#{field} #{op} ?", value])
189
184
  else
190
- res = res.where(["#{field} = ?", value])
185
+ res = res.where(["#{table_name}.#{field} = ?", value])
191
186
  end
192
187
  end
193
188
 
@@ -37,13 +37,12 @@ module Netzke
37
37
  class_attribute :config_tool_available
38
38
  self.config_tool_available = true
39
39
 
40
- class_attribute :default_config
41
- self.default_config = {} # To be filled in
42
-
43
40
  include self::Services
44
41
  include self::Fields
45
42
  include Netzke::Basepack::DataAccessor
46
43
 
44
+ delegates_to_dsl :model, :record_id
45
+
47
46
  action :apply do
48
47
  {
49
48
  :text => I18n.t('netzke.basepack.form_panel.actions.apply'),
@@ -72,6 +71,8 @@ module Netzke
72
71
  super.tap do |sup|
73
72
  configure_locked(sup)
74
73
  configure_bbar(sup)
74
+
75
+ sup[:record_id] = sup[:record] = nil if sup[:multi_edit] # never set record_id in multi-edit mode
75
76
  end
76
77
  end
77
78
 
@@ -80,7 +81,7 @@ module Netzke
80
81
  end
81
82
 
82
83
  def configure_bbar(c)
83
- c[:bbar] = [:apply.action] if c[:bbar].nil?
84
+ c[:bbar] = [:apply.action] if c[:bbar].nil? && !c[:read_only]
84
85
  end
85
86
 
86
87
  # Extra JavaScripts and stylesheets
@@ -185,7 +185,7 @@ module Netzke
185
185
  def attr_type_to_xtype_map
186
186
  {
187
187
  :integer => :numberfield,
188
- :boolean => :checkboxfield,
188
+ :boolean => config[:multi_edit] ? :tricheckbox : :checkboxfield,
189
189
  :date => :datefield,
190
190
  # WIP: waiting for datetime.js implementation for ExtJS 4
191
191
  # :datetime => :datetimefield,
@@ -24,7 +24,7 @@
24
24
  trackResetOnLoad: true,
25
25
  }
26
26
 
27
- if (!this.bbar) this.bbar = {xtype: 'toolbar'}; // an empty bbar by default, so that we can dynamically add buttons
27
+ if (!this.bbar && !this.readOnly) this.bbar = {xtype: 'toolbar'}; // an empty bbar by default, so that we can dynamically add buttons
28
28
 
29
29
  // Custom error reader. We don't use it to process form values, but rather to normalize the response from the server in case of "real" (iframe) form submit.
30
30
  ErrorReader = function(){};
@@ -55,7 +55,7 @@
55
55
  if (this.record) { this.setFormValues(this.record); }
56
56
 
57
57
  // render in display mode?
58
- if (this.locked) this.setReadonlyMode(true);
58
+ if (this.locked || this.readOnly) this.setReadonlyMode(true);
59
59
  },
60
60
 
61
61
  onEdit: function(){
@@ -201,7 +201,7 @@
201
201
  // this.getForm().cleanDestroyed(); // because fields inside of composite fields are not auto-cleaned!
202
202
  this.doLayout();
203
203
  this.inReadonlyMode = onOff;
204
- this.updateToolbar();
204
+ if (this.mode == "lockable") this.updateToolbar();
205
205
  },
206
206
 
207
207
  // recursively extract field names
@@ -183,8 +183,45 @@ module Netzke
183
183
  js_include(ex.join("#{File.dirname(__FILE__)}/grid_panel/javascripts/rows-dd.js"))
184
184
  end
185
185
 
186
+ # Allows children classes to simply do
187
+ #
188
+ # model "User"
189
+ delegates_to_dsl :model, :add_form_config, :add_form_window_config, :edit_form_config, :edit_form_window_config, :multi_edit_form_config, :multi_edit_form_window_config
190
+
191
+ # Inject some handy DSL methods into the child classes.
192
+ def self.inherited(base)
193
+ super
194
+
195
+ base.class_eval do
196
+ class << self
197
+ def column(name, config = {})
198
+ columns = self.read_inheritable_attribute(:columns) || []
199
+ columns << config.merge(:name => name.to_s)
200
+ self.write_inheritable_attribute(:columns, columns)
201
+ end
202
+
203
+ def override_column(name, config)
204
+ columns = self.read_inheritable_attribute(:overridden_columns) || {}
205
+ columns.merge!(name.to_sym => config)
206
+ self.write_inheritable_attribute(:overridden_columns, columns)
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ def configuration
213
+ super.tap do |c|
214
+ c[:columns] ||= self.class.read_inheritable_attribute(:columns)
215
+
216
+ # user-passed :override_columns option should get deep_merged with the defaults
217
+ c[:override_columns] = (self.class.read_inheritable_attribute(:overridden_columns) || {}).deep_merge(c[:override_columns] || {})
218
+ end
219
+ end
220
+
186
221
  def js_config #:nodoc:
187
- super.merge({
222
+ res = super
223
+ res.merge({
224
+ :title => res[:title] || self.class.js_properties[:title] || data_class.name.pluralize,
188
225
  :bbar => config.has_key?(:bbar) ? config[:bbar] : default_bbar,
189
226
  :context_menu => config.has_key?(:context_menu) ? config[:context_menu] : default_context_menu,
190
227
  :columns => columns(:with_meta => true), # columns
@@ -307,60 +344,74 @@ module Netzke
307
344
  end
308
345
 
309
346
  component :add_form do
347
+ form_config = {
348
+ :class_name => "Netzke::Basepack::FormPanel",
349
+ :model => config[:model],
350
+ :persistent_config => config[:persistent_config],
351
+ :strong_default_attrs => config[:strong_default_attrs],
352
+ :border => true,
353
+ :bbar => false,
354
+ :prevent_header => true,
355
+ :mode => config[:mode],
356
+ :record => data_class.new(columns_default_values)
357
+ }
358
+
359
+ # Only provide default form fields when no custom class_name is specified for the form
360
+ form_config[:items] = default_fields_for_forms unless config[:add_form_config] && config[:add_form_config][:class_name]
361
+
310
362
  {
311
363
  :lazy_loading => true,
312
364
  :class_name => "Netzke::Basepack::GridPanel::RecordFormWindow",
313
365
  :title => "Add #{data_class.model_name.human}",
314
366
  :button_align => "right",
315
- :items => [{
316
- :class_name => "Netzke::Basepack::FormPanel",
317
- :model => config[:model],
318
- :items => default_fields_for_forms,
319
- :persistent_config => config[:persistent_config],
320
- :strong_default_attrs => config[:strong_default_attrs],
321
- :border => true,
322
- :bbar => false,
323
- :prevent_header => true,
324
- :mode => config[:mode],
325
- :record => data_class.new(columns_default_values)
326
- }.deep_merge(config[:add_form_config] || {})]
367
+ :items => [form_config.deep_merge(config[:add_form_config] || {})]
327
368
  }.deep_merge(config[:add_form_window_config] || {})
328
369
  end
329
370
 
330
371
  component :edit_form do
331
- {
332
- :lazy_loading => true,
333
- :class_name => "Netzke::Basepack::GridPanel::RecordFormWindow",
334
- :title => "Edit #{data_class.model_name.human}",
335
- :button_align => "right",
336
- :items => [{
372
+ form_config = {
337
373
  :class_name => "Netzke::Basepack::FormPanel",
338
374
  :model => config[:model],
339
- :items => default_fields_for_forms,
340
375
  :persistent_config => config[:persistent_config],
341
376
  :bbar => false,
342
377
  :prevent_header => true,
343
378
  :mode => config[:mode]
344
379
  # :record_id gets assigned by deliver_component dynamically, at the moment of loading
345
- }.deep_merge(config[:edit_form_config] || {})]
380
+ }
381
+
382
+ # Only provide default form fields when no custom class_name is specified for the form
383
+ form_config[:items] = default_fields_for_forms unless config[:edit_form_config] && config[:edit_form_config][:class_name]
384
+
385
+ {
386
+ :lazy_loading => true,
387
+ :class_name => "Netzke::Basepack::GridPanel::RecordFormWindow",
388
+ :title => "Edit #{data_class.model_name.human}",
389
+ :button_align => "right",
390
+ :items => [form_config.deep_merge(config[:edit_form_config] || {})]
346
391
  }.deep_merge(config[:edit_form_window_config] || {})
347
392
  end
348
393
 
349
394
  component :multi_edit_form do
395
+ form_config = {
396
+ :class_name => "Netzke::Basepack::FormPanel",
397
+ :multi_edit => true,
398
+ :model => config[:model],
399
+ :persistent_config => config[:persistent_config],
400
+ :bbar => false,
401
+ :prevent_header => true,
402
+ :name => "multi_edit_form0", # we detect multi-edit form submission by its name
403
+ :mode => config[:mode]
404
+ }
405
+
406
+ # Only provide default form fields when no custom class_name is specified for the form
407
+ form_config[:items] = default_fields_for_forms unless config[:multi_edit_form_config] && config[:multi_edit_form_config][:class_name]
408
+
350
409
  {
351
410
  :lazy_loading => true,
352
411
  :class_name => "Netzke::Basepack::GridPanel::RecordFormWindow",
353
412
  :title => "Edit #{data_class.model_name.human.pluralize}",
354
413
  :button_align => "right",
355
- :items => [{
356
- :class_name => "Netzke::Basepack::GridPanel::MultiEditForm",
357
- :model => config[:model],
358
- :items => default_fields_for_forms,
359
- :persistent_config => config[:persistent_config],
360
- :bbar => false,
361
- :prevent_header => true,
362
- :mode => config[:mode]
363
- }.deep_merge(config[:multi_edit_form_config] || {})]
414
+ :items => [form_config.deep_merge(config[:multi_edit_form_config] || {})]
364
415
  }.deep_merge(config[:multi_edit_form_window_config] || {})
365
416
  end
366
417