netzke-basepack 0.7.1 → 0.7.2

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