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
@@ -3,67 +3,97 @@ module Netzke
|
|
3
3
|
class GridPanel < Netzke::Base
|
4
4
|
module Columns
|
5
5
|
extend ActiveSupport::Concern
|
6
|
+
extend ActiveSupport::Memoizable
|
7
|
+
|
8
|
+
# module ClassMethods
|
9
|
+
# # Columns to be displayed by the FieldConfigurator, "meta-columns". Each corresponds to a configuration
|
10
|
+
# # option for each column in the grid.
|
11
|
+
# def meta_columns
|
12
|
+
# [
|
13
|
+
# # Whether the column will be present in the grid, also in :hidden or :meta state. The value for this column will
|
14
|
+
# # always be sent to/from the JS grid to the server
|
15
|
+
# {:name => "included", :attr_type => :boolean, :width => 40, :header => "Incl", :default_value => true},
|
16
|
+
#
|
17
|
+
# # The name of the column. May be any accessible method or attribute of the data_class.
|
18
|
+
# {:name => "name", :attr_type => :string, :width => 200},
|
19
|
+
#
|
20
|
+
# # The header for the column.
|
21
|
+
# {:name => "label", :attr_type => :string, :width => 200, :header => "Header"},
|
22
|
+
#
|
23
|
+
# # The default value of this column. Is used when a new row in the grid gets created.
|
24
|
+
# {:name => "default_value", :attr_type => :string, :width => 200},
|
25
|
+
#
|
26
|
+
# # Options for drop-downs
|
27
|
+
# {:name => "combobox_options", :attr_type => :string, :editor => :textarea, :width => 200},
|
28
|
+
#
|
29
|
+
# # Whether the column is editable in the grid.
|
30
|
+
# {:name => "read_only", :attr_type => :boolean, :header => "R/O", :tooltip => "Read-only"},
|
31
|
+
#
|
32
|
+
# # Whether the column will be in the hidden state (hide/show columns from the column menu, if it's enabled).
|
33
|
+
# {:name => "hidden", :attr_type => :boolean},
|
34
|
+
#
|
35
|
+
# # Whether the column should have "grid filters" enabled
|
36
|
+
# # (see here: http://www.extjs.com/deploy/dev/examples/grid-filtering/grid-filter-local.html)
|
37
|
+
# {:name => "with_filters", :attr_type => :boolean, :default_value => true, :header => "Filters"},
|
38
|
+
#
|
39
|
+
# #
|
40
|
+
# # Below some rarely used parameters, hidden by default (you can always un-hide them from the column menu).
|
41
|
+
# #
|
42
|
+
#
|
43
|
+
# # The column's width
|
44
|
+
# {:name => "width", :attr_type => :integer, :hidden => true},
|
45
|
+
#
|
46
|
+
# # Whether the column should be hideable
|
47
|
+
# {:name => "hideable", :attr_type => :boolean, :default_value => true, :hidden => true},
|
48
|
+
#
|
49
|
+
# # Whether the column should be sortable (why change it? normally it's hardcoded)
|
50
|
+
# {:name => "sortable", :attr_type => :boolean, :default_value => true, :hidden => true},
|
51
|
+
# ]
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# end
|
6
55
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
{:name => "label", :attr_type => :string, :width => 200, :header => "Header"},
|
21
|
-
|
22
|
-
# The default value of this column. Is used when a new row in the grid gets created.
|
23
|
-
{:name => "default_value", :attr_type => :string, :width => 200},
|
24
|
-
|
25
|
-
# Options for drop-downs
|
26
|
-
{:name => "combobox_options", :attr_type => :string, :editor => :textarea, :width => 200},
|
27
|
-
|
28
|
-
# Whether the column is editable in the grid.
|
29
|
-
{:name => "read_only", :attr_type => :boolean, :header => "R/O", :tooltip => "Read-only"},
|
30
|
-
|
31
|
-
# Whether the column will be in the hidden state (hide/show columns from the column menu, if it's enabled).
|
32
|
-
{:name => "hidden", :attr_type => :boolean},
|
33
|
-
|
34
|
-
# Whether the column should have "grid filters" enabled
|
35
|
-
# (see here: http://www.extjs.com/deploy/dev/examples/grid-filtering/grid-filter-local.html)
|
36
|
-
{:name => "with_filters", :attr_type => :boolean, :default_value => true, :header => "Filters"},
|
37
|
-
|
38
|
-
#
|
39
|
-
# Below some rarely used parameters, hidden by default (you can always un-hide them from the column menu).
|
40
|
-
#
|
56
|
+
# Normalized columns for the grid, e.g.:
|
57
|
+
# [{:name => :id, :hidden => true, ...}, {:name => :name, :editable => false, ...}, ...]
|
58
|
+
# Possible options:
|
59
|
+
# * +with_excluded+ - when set to true, also excluded columns will be returned (handy for dynamic column configuration)
|
60
|
+
# * +with_meta+ - when set to true, the meta column will be included as the last column
|
61
|
+
def columns(options = {})
|
62
|
+
[].tap do |cols|
|
63
|
+
if loaded_columns = load_columns
|
64
|
+
filter_out_excluded_columns(loaded_columns) unless options[:with_excluded]
|
65
|
+
cols.concat(reverse_merge_equally_named_columns(loaded_columns, initial_columns(options[:with_excluded])))
|
66
|
+
else
|
67
|
+
cols.concat(initial_columns(options[:with_excluded]))
|
68
|
+
end
|
41
69
|
|
42
|
-
|
43
|
-
|
70
|
+
append_meta_column(cols) if options[:with_meta]
|
71
|
+
end
|
72
|
+
end
|
44
73
|
|
45
|
-
|
46
|
-
{:name => "hideable", :attr_type => :boolean, :default_value => true, :hidden => true},
|
74
|
+
memoize :columns
|
47
75
|
|
48
|
-
|
49
|
-
|
50
|
-
|
76
|
+
def append_meta_column(cols)
|
77
|
+
cols << {}.tap do |c|
|
78
|
+
c.merge!(
|
79
|
+
:name => "_meta",
|
80
|
+
:meta => true,
|
81
|
+
:getter => lambda do |r|
|
82
|
+
meta_data(r)
|
83
|
+
end
|
84
|
+
)
|
85
|
+
c[:default_value] = meta_default_data if meta_default_data.present?
|
51
86
|
end
|
87
|
+
end
|
52
88
|
|
89
|
+
# default_value for the meta column; used when a new record is being created in the grid
|
90
|
+
def meta_default_data
|
91
|
+
get_default_association_values.present? ? { :association_values => get_default_association_values.literalize_keys } : {}
|
53
92
|
end
|
54
93
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
@columns ||= begin
|
59
|
-
if cols = load_columns
|
60
|
-
filter_out_excluded_columns(cols) if only_included
|
61
|
-
reverse_merge_equally_named_columns(cols, initial_columns)
|
62
|
-
cols
|
63
|
-
else
|
64
|
-
initial_columns(only_included)
|
65
|
-
end
|
66
|
-
end
|
94
|
+
# Override it when you need extra meta data to be passed through the meta column
|
95
|
+
def meta_data(r)
|
96
|
+
{ :association_values => get_association_values(r).literalize_keys }
|
67
97
|
end
|
68
98
|
|
69
99
|
# Columns as a hash, for easier access to a specific column
|
@@ -80,7 +110,7 @@ module Netzke
|
|
80
110
|
end
|
81
111
|
|
82
112
|
# Columns that represent a smart merge of default_columns and columns passed during the configuration.
|
83
|
-
def initial_columns(
|
113
|
+
def initial_columns(with_excluded = false)
|
84
114
|
# Normalize here, as from the config we can get symbols (names) instead of hashes
|
85
115
|
columns_from_config = config[:columns] && normalize_attrs(config[:columns])
|
86
116
|
|
@@ -101,7 +131,7 @@ module Netzke
|
|
101
131
|
columns_for_create = default_columns
|
102
132
|
end
|
103
133
|
|
104
|
-
filter_out_excluded_columns(columns_for_create)
|
134
|
+
filter_out_excluded_columns(columns_for_create) unless with_excluded
|
105
135
|
|
106
136
|
# Make the column config complete with the defaults
|
107
137
|
columns_for_create.each do |c|
|
@@ -143,7 +173,7 @@ module Netzke
|
|
143
173
|
end
|
144
174
|
|
145
175
|
def set_default_editor(c)
|
146
|
-
c[:editor] ||= editor_for_attr_type(c[:attr_type])
|
176
|
+
# c[:editor] ||= editor_for_attr_type(c[:attr_type]) # This is done in JS!
|
147
177
|
c[:editor] = {:xtype => c[:editor]} if c[:editor].is_a?(Symbol)
|
148
178
|
end
|
149
179
|
|
@@ -157,19 +187,21 @@ module Netzke
|
|
157
187
|
end
|
158
188
|
|
159
189
|
def set_default_editable(c)
|
160
|
-
|
161
|
-
|
162
|
-
|
190
|
+
if c[:editable].nil?
|
191
|
+
not_editable_if = primary_key_attr?(c)
|
192
|
+
not_editable_if ||= c[:virtual] && !association_attr?(c[:name])
|
193
|
+
not_editable_if ||= c.delete(:read_only)
|
163
194
|
|
164
|
-
|
165
|
-
|
166
|
-
|
195
|
+
editable_if = data_class.column_names.include?(c[:name])
|
196
|
+
editable_if ||= data_class.instance_methods.map(&:to_s).include?("#{c[:name]}=")
|
197
|
+
editable_if ||= association_attr?(c[:name])
|
167
198
|
|
168
|
-
|
199
|
+
c[:editable] = editable_if && !not_editable_if
|
200
|
+
end
|
169
201
|
end
|
170
202
|
|
171
203
|
def set_default_sortable(c)
|
172
|
-
c[:sortable] = !c[:virtual] if c[:sortable].nil?
|
204
|
+
c[:sortable] = !c[:virtual] if c[:sortable].nil? # TODO: optimize - don't set it to false
|
173
205
|
end
|
174
206
|
|
175
207
|
def set_default_filterable(c)
|
@@ -182,7 +214,7 @@ module Netzke
|
|
182
214
|
end
|
183
215
|
|
184
216
|
def editor_for_association
|
185
|
-
:
|
217
|
+
:netzkeremotecombo
|
186
218
|
end
|
187
219
|
|
188
220
|
# Returns a hash that maps a column type to the editor xtype. Override if you want different editors.
|
@@ -191,7 +223,7 @@ module Netzke
|
|
191
223
|
:integer => :numberfield,
|
192
224
|
:boolean => :checkbox,
|
193
225
|
:date => :datefield,
|
194
|
-
:datetime => :
|
226
|
+
:datetime => :datetimefield,
|
195
227
|
:text => :textarea,
|
196
228
|
:string => :textfield
|
197
229
|
}
|
@@ -199,7 +231,7 @@ module Netzke
|
|
199
231
|
|
200
232
|
# Detects an association column and sets up the proper editor.
|
201
233
|
def detect_association(c)
|
202
|
-
assoc, assoc_method =
|
234
|
+
assoc, assoc_method = assoc_and_assoc_method_for_column(c)
|
203
235
|
if assoc
|
204
236
|
assoc_column = assoc.klass.columns_hash[assoc_method]
|
205
237
|
assoc_method_type = assoc_column.try(:type)
|
@@ -209,20 +241,11 @@ module Netzke
|
|
209
241
|
c[:editor] ||= editor_for_attr_type(assoc_method_type)
|
210
242
|
else
|
211
243
|
c[:editor] ||= assoc_method_type == :boolean ? editor_for_attr_type(:boolean) : editor_for_association
|
244
|
+
c[:assoc] = true
|
212
245
|
end
|
213
246
|
end
|
214
247
|
end
|
215
248
|
|
216
|
-
def get_assoc_and_method(c)
|
217
|
-
if c[:name].index("__")
|
218
|
-
assoc_name, assoc_method = c[:name].split('__')
|
219
|
-
assoc = data_class.reflect_on_association(assoc_name.to_sym)
|
220
|
-
[assoc, assoc_method]
|
221
|
-
else
|
222
|
-
[nil, nil]
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
249
|
# Default fields that will be displayed in the Add/Edit/Search forms
|
227
250
|
# When overriding this method, keep in mind that the fields inside the layout must be expanded (each field represented by a hash, not just a symbol)
|
228
251
|
def default_fields_for_forms
|
@@ -239,20 +262,35 @@ module Netzke
|
|
239
262
|
field_config = {:name => c[:name]}
|
240
263
|
|
241
264
|
# scopes for combobox options
|
242
|
-
field_config[:scopes] = c[:editor]
|
265
|
+
field_config[:scopes] = c[:editor][:scopes] if c[:editor].is_a?(Hash)
|
243
266
|
|
244
267
|
field_config
|
245
268
|
end
|
246
269
|
end
|
247
270
|
|
248
271
|
# default_fields_for_forms extended with default values (for new-record form)
|
249
|
-
def default_fields_for_forms_with_default_values
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
272
|
+
# def default_fields_for_forms_with_default_values
|
273
|
+
# res = default_fields_for_forms.dup
|
274
|
+
# each_attr_in(res) do |a|
|
275
|
+
# attr_name = a[:name].to_sym
|
276
|
+
# a[:value] = a[:default_value] || columns_hash[attr_name].try(:fetch, :default_value, nil) || data_class.netzke_attribute_hash[attr_name].try(:fetch, :default_value, nil)
|
277
|
+
# end
|
278
|
+
# res
|
279
|
+
# end
|
280
|
+
|
281
|
+
def columns_default_values
|
282
|
+
columns.inject({}) do |r,c|
|
283
|
+
assoc, assoc_method = assoc_and_assoc_method_for_column(c)
|
284
|
+
if c[:default_value].nil?
|
285
|
+
r
|
286
|
+
else
|
287
|
+
if assoc
|
288
|
+
r.merge(assoc.options[:foreign_key] || assoc.name.to_s.foreign_key => c[:default_value])
|
289
|
+
else
|
290
|
+
r.merge(c[:name] => c[:default_value])
|
291
|
+
end
|
292
|
+
end
|
254
293
|
end
|
255
|
-
res
|
256
294
|
end
|
257
295
|
|
258
296
|
# Recursively traversess items (an array) and yields each found field (a hash with :name set)
|
@@ -1,34 +1,29 @@
|
|
1
1
|
{
|
2
2
|
onSearch: function(el){
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
if (this.searchWindow) {
|
4
|
+
this.searchWindow.show();
|
5
|
+
} else {
|
6
|
+
this.loadComponent({name: 'search_form', callback: function(win){
|
7
|
+
this.searchWindow = win;
|
8
|
+
var currentConditionsString = this.getStore().baseParams.extra_conditions;
|
9
|
+
if (currentConditionsString) {
|
10
|
+
win.items.first().getForm().setValues(Ext.decode(currentConditionsString));
|
11
|
+
}
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
win.items.first().on('apply', function(){
|
14
|
+
win.onSearch();
|
15
|
+
return false; // do not propagate the 'apply' event
|
16
|
+
}, this);
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
for (var k in searchConditions) {
|
22
|
-
if (searchConditions[k].length > 0) {
|
23
|
-
filtered = true;
|
24
|
-
break;
|
25
|
-
}
|
18
|
+
win.on('hide', function(){
|
19
|
+
var query = win.getQuery();
|
20
|
+
if (win.closeRes == 'search'){
|
21
|
+
this.getStore().baseParams.query = Ext.encode(query);
|
22
|
+
this.getStore().load();
|
26
23
|
}
|
27
|
-
el.toggle(
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
}, this);
|
32
|
-
}, scope: this});
|
24
|
+
el.toggle(query.length > 0); // toggle based on the state
|
25
|
+
}, this);
|
26
|
+
}, scope: this});
|
27
|
+
}
|
33
28
|
}
|
34
29
|
}
|
@@ -10,11 +10,15 @@
|
|
10
10
|
this.plugins = []; // checkbox colums is a special case, being a plugin
|
11
11
|
|
12
12
|
var filters = [];
|
13
|
+
var metaColumn;
|
13
14
|
|
14
15
|
// Run through columns and set up different configuration for each
|
15
16
|
Ext.each(this.columns, function(c, i){
|
16
17
|
// We will not use meta columns as actual columns (not even hidden) - only to create the records
|
17
|
-
if (c.meta)
|
18
|
+
if (c.meta) {
|
19
|
+
metaColumn = c;
|
20
|
+
return;
|
21
|
+
}
|
18
22
|
|
19
23
|
// Apply default column config
|
20
24
|
Ext.applyIf(c, this.defaultColumnConfig);
|
@@ -25,13 +29,14 @@
|
|
25
29
|
// Automatically calculated default values
|
26
30
|
if (!c.header) {c.header = c.label || c.name.humanize()}
|
27
31
|
|
28
|
-
//
|
29
|
-
if (
|
30
|
-
|
31
|
-
} else {
|
32
|
-
c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
|
32
|
+
// Set initial association values
|
33
|
+
if (this.inlineData) {
|
34
|
+
this.associationValues = this.inlineData.setAssociationValues;
|
33
35
|
}
|
34
36
|
|
37
|
+
// normalize editor
|
38
|
+
this.normalizeEditor(c);
|
39
|
+
|
35
40
|
// if comboboxOptions are provided, we render a combobox instead of textfield
|
36
41
|
if (c.comboboxOptions && c.editor.xtype === "textfield") {
|
37
42
|
c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
|
@@ -39,7 +44,7 @@
|
|
39
44
|
|
40
45
|
// collect filters
|
41
46
|
if (c.filterable){
|
42
|
-
filters.push({type:this.filterTypeForAttrType(c.attrType), dataIndex:c.name});
|
47
|
+
filters.push({type: this.filterTypeForAttrType(c.attrType), dataIndex: c.name});
|
43
48
|
}
|
44
49
|
|
45
50
|
if (c.editor && c.editor.xtype == 'checkbox') {
|
@@ -51,38 +56,39 @@
|
|
51
56
|
// a "normal" column, not a plugin
|
52
57
|
if (!c.readOnly && !this.prohibitUpdate) {
|
53
58
|
// c.editor contains complete config of the editor
|
54
|
-
var xtype = c.editor.xtype;
|
55
59
|
c.editor = Ext.apply({
|
56
60
|
parentId: this.id,
|
57
61
|
name: c.name,
|
58
|
-
selectOnFocus:true
|
62
|
+
selectOnFocus: true
|
59
63
|
}, c.editor);
|
60
64
|
} else {
|
61
65
|
c.editor = null;
|
62
66
|
}
|
63
67
|
|
64
|
-
// Normalize the renderer
|
65
68
|
this.normalizeRenderer(c);
|
66
69
|
|
67
|
-
|
68
|
-
|
69
|
-
//
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
70
|
+
this.setDefaultColumnType(c);
|
71
|
+
|
72
|
+
// Set rendeder for association columns (the one displaying associations by the specified method instead of id)
|
73
|
+
if (c.assoc) {
|
74
|
+
c.scope = this;
|
75
|
+
var passedRenderer = c.renderer; // renderer we got from normalizeRenderer
|
76
|
+
c.renderer = function(value, a, r, ri, ci){
|
77
|
+
var editor = this.getColumnModel().getColumnAt(ci).getEditor();
|
78
|
+
var recordFromStore = editor && editor.getStore && editor.getStore().getById(value);
|
79
|
+
var renderedValue;
|
80
|
+
if (recordFromStore) {
|
81
|
+
renderedValue = recordFromStore.get('field2');
|
82
|
+
} else if (c.assoc && r.get('_meta')) {
|
83
|
+
renderedValue = r.get('_meta').associationValues[c.name] || value;
|
84
|
+
} else {
|
85
|
+
renderedValue = value;
|
86
|
+
}
|
87
|
+
|
88
|
+
return passedRenderer ? passedRenderer.call(this, renderedValue) : renderedValue;
|
89
|
+
};
|
90
|
+
}
|
91
|
+
|
86
92
|
}
|
87
93
|
|
88
94
|
}, this);
|
@@ -90,15 +96,47 @@
|
|
90
96
|
/* ... and done with columns */
|
91
97
|
|
92
98
|
// Filters
|
99
|
+
this.gridFilters = new Ext.ux.grid.GridFilters({filters:filters, encode: true});
|
93
100
|
if (this.enableColumnFilters) {
|
94
|
-
this.plugins.push(
|
101
|
+
this.plugins.push(this.gridFilters);
|
95
102
|
}
|
96
103
|
|
97
104
|
// Create Ext.data.Record constructor specific for our particular column configuration
|
98
105
|
this.recordConfig = [];
|
99
|
-
Ext.each(this.columns, function(column){
|
106
|
+
Ext.each(this.columns, function(column){
|
107
|
+
var extraConfig = {};
|
108
|
+
if (column.attrType == 'datetime') {
|
109
|
+
extraConfig.type = 'date';
|
110
|
+
extraConfig.dateFormat = 'Y-m-d g:i:s';
|
111
|
+
};
|
112
|
+
this.recordConfig.push(Ext.apply({name: column.name, defaultValue: column.defaultValue}, extraConfig));
|
113
|
+
}, this);
|
100
114
|
this.Row = Ext.data.Record.create(this.recordConfig);
|
101
115
|
|
116
|
+
// After we created the record (model), we can get rid of the meta column
|
117
|
+
this.columns.remove(metaColumn);
|
118
|
+
|
119
|
+
// Prepare column model config with columns in the correct order
|
120
|
+
var colModelConfig = [];
|
121
|
+
|
122
|
+
Ext.each(this.columnsOrder, function(c) {
|
123
|
+
var mainColConfig;
|
124
|
+
Ext.each(this.columns, function(oc) {
|
125
|
+
if (c.name === oc.name) {
|
126
|
+
mainColConfig = oc;
|
127
|
+
return false;
|
128
|
+
}
|
129
|
+
});
|
130
|
+
|
131
|
+
colModelConfig.push(Ext.apply(mainColConfig, c));
|
132
|
+
}, this);
|
133
|
+
|
134
|
+
// We don't need original columns any longer
|
135
|
+
delete this.columns;
|
136
|
+
|
137
|
+
// ... instead - define a custom column model
|
138
|
+
this.colModel = new Ext.grid.ColumnModel(colModelConfig);
|
139
|
+
|
102
140
|
// Drag'n'Drop
|
103
141
|
if (this.enableRowsReordering){
|
104
142
|
this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
|
@@ -107,74 +145,45 @@
|
|
107
145
|
this.plugins.push(this.ddPlugin);
|
108
146
|
}
|
109
147
|
|
110
|
-
//
|
111
|
-
|
112
|
-
// as we are going to subscribe to its events
|
113
|
-
var connection = new Ext.data.Connection({
|
114
|
-
url: this.endpointUrl("get_data"),
|
115
|
-
extraParams: {
|
116
|
-
authenticity_token : Netzke.authenticityToken
|
117
|
-
},
|
118
|
-
|
119
|
-
// inform Ext.Ajax about our events
|
120
|
-
listeners: {
|
121
|
-
beforerequest: function(){
|
122
|
-
Ext.Ajax.fireEvent('beforerequest', arguments);
|
123
|
-
},
|
124
|
-
requestexception: function(){
|
125
|
-
Ext.Ajax.fireEvent('requestexception', arguments);
|
126
|
-
},
|
127
|
-
requestcomplete: function(){
|
128
|
-
Ext.Ajax.fireEvent('requestcomplete', arguments);
|
129
|
-
}
|
130
|
-
}
|
131
|
-
});
|
148
|
+
// DirectProxy that uses our Ext.direct provider
|
149
|
+
this.proxy = new Ext.data.DirectProxy({directFn: Netzke.providers[this.id].getData});
|
132
150
|
|
133
|
-
|
134
|
-
|
135
|
-
var response =
|
151
|
+
this.proxy.on('load', function (self, t, options) {
|
152
|
+
// besides getting data into the store, we may also get commands to execute
|
153
|
+
var response = t.result;
|
136
154
|
|
137
155
|
// delete data-related properties
|
138
156
|
Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
|
139
157
|
this.bulkExecute(response);
|
140
158
|
}, this);
|
141
159
|
|
142
|
-
// HttpProxy that uses our custom connection
|
143
|
-
var httpProxy = new Ext.data.HttpProxy(connection);
|
144
|
-
|
145
160
|
// Data store
|
146
|
-
this.store =
|
147
|
-
pruneModifiedRecords: true,
|
148
|
-
proxy: this.proxy = httpProxy,
|
149
|
-
reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
|
150
|
-
remoteSort: true,
|
151
|
-
listeners:{'loadexception':{
|
152
|
-
fn:this.loadExceptionHandler,
|
153
|
-
scope:this
|
154
|
-
}}
|
155
|
-
});
|
161
|
+
this.store = this.buildStore();
|
156
162
|
|
157
163
|
// Normalize bottom bar
|
158
|
-
this.bbar = (this.enablePagination) ? new Ext.PagingToolbar({
|
164
|
+
this.bbar = (this.enablePagination) ? new Ext.PagingToolbar(Ext.copyTo({
|
159
165
|
pageSize : this.rowsPerPage,
|
160
166
|
items : this.bbar ? ["-"].concat(this.bbar) : [],
|
161
167
|
store : this.store,
|
162
|
-
emptyMsg:
|
163
|
-
displayInfo: true
|
164
|
-
|
168
|
+
emptyMsg: this.i18n.empty,
|
169
|
+
displayInfo: true,
|
170
|
+
plugins: this.gridFilters ? [this.gridFilters] : []
|
171
|
+
}, this.i18n, 'emptyMsg,firstText,prevText,nextText,lastText,beforePageText,afterPageText,refreshText,displayMsg')) : this.bbar;
|
165
172
|
|
166
173
|
// Selection model
|
167
174
|
if (!this.sm) this.sm = new Ext.grid.RowSelectionModel();
|
168
175
|
|
169
|
-
|
170
|
-
|
176
|
+
this.view = this.buildView();
|
177
|
+
|
178
|
+
// Now let Ext.grid.EditorGridPanel do the rest (original initComponent)
|
171
179
|
Netzke.classes.Basepack.GridPanel.superclass.initComponent.call(this);
|
172
180
|
|
173
|
-
//
|
174
|
-
if (this.
|
181
|
+
// Persistence-related events
|
182
|
+
if (this.persistence) {
|
183
|
+
// Hidden change event
|
184
|
+
this.getColumnModel().on('hiddenchange', this.onColumnHiddenChange, this);
|
175
185
|
|
176
|
-
|
177
|
-
if (this.persistentConfig) {
|
186
|
+
// Inform the server part about column operations
|
178
187
|
this.on('columnresize', this.onColumnResize, this);
|
179
188
|
this.on('columnmove', this.onColumnMove, this);
|
180
189
|
}
|
@@ -210,9 +219,9 @@
|
|
210
219
|
this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
|
211
220
|
}
|
212
221
|
|
213
|
-
// inlineData may also contain commands (TODO: make it DRY)
|
222
|
+
// inlineData may also contain commands (TODO: make it DRY, as this code repeats in multiple places...)
|
214
223
|
// delete data-related properties
|
215
|
-
Ext.each(['data', 'total', 'success'], function(property){delete this.inlineData[property];}, this);
|
224
|
+
Ext.each(['data', 'total', 'success'], function(property){ delete this.inlineData[property]; }, this);
|
216
225
|
this.bulkExecute(this.inlineData);
|
217
226
|
}
|
218
227
|
|
@@ -230,6 +239,33 @@
|
|
230
239
|
|
231
240
|
// GridView
|
232
241
|
this.getView().getRowClass = this.defaultGetRowClass;
|
242
|
+
|
243
|
+
// When starting editing as assocition column, pre-load the combobox store from the meta column, so that we don't see the real value of this cell (the id of the associated record), but rather the associated record by the configured method.
|
244
|
+
this.on('beforeedit', function(e){
|
245
|
+
var column = this.getColumnModel().getColumnById(this.getColumnModel().getColumnId(e.column));
|
246
|
+
if (column.assoc && column.getEditor().isXType('combo') && e.record.get('_meta')) {
|
247
|
+
column.getEditor().getStore().loadData({
|
248
|
+
data: [[e.record.get(e.field), e.record.get('_meta').associationValues[e.field]]]
|
249
|
+
});
|
250
|
+
}
|
251
|
+
}, this);
|
252
|
+
},
|
253
|
+
|
254
|
+
buildStore: function() {
|
255
|
+
return new Ext.data.Store({
|
256
|
+
pruneModifiedRecords: true,
|
257
|
+
proxy: this.proxy,
|
258
|
+
reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
|
259
|
+
remoteSort: true,
|
260
|
+
listeners:{'loadexception':{
|
261
|
+
fn:this.loadExceptionHandler,
|
262
|
+
scope:this
|
263
|
+
}}
|
264
|
+
});
|
265
|
+
},
|
266
|
+
|
267
|
+
buildView: function() {
|
268
|
+
return null;
|
233
269
|
},
|
234
270
|
|
235
271
|
filterTypeForAttrType: function(attrType){
|
@@ -238,9 +274,9 @@
|
|
238
274
|
decimal :'Numeric',
|
239
275
|
datetime:'Date',
|
240
276
|
date :'Date',
|
241
|
-
string :'String'
|
277
|
+
string :'String',
|
278
|
+
'boolean': 'Boolean'
|
242
279
|
};
|
243
|
-
map['boolean'] = "Boolean"; // "boolean" is a JS reserved word
|
244
280
|
return map[attrType] || 'String';
|
245
281
|
},
|
246
282
|
|
@@ -249,11 +285,15 @@
|
|
249
285
|
"float" : "numberfield",
|
250
286
|
"boolean": "checkbox",
|
251
287
|
decimal : "numberfield",
|
252
|
-
datetime : "
|
288
|
+
datetime : "datetimefield",
|
253
289
|
date : "datefield",
|
254
290
|
string : "textfield"
|
255
291
|
},
|
256
292
|
|
293
|
+
setAssociationValues: function(assocObj) {
|
294
|
+
this.associationValues = assocObj;
|
295
|
+
},
|
296
|
+
|
257
297
|
onAdd: function(){
|
258
298
|
var r = new this.Row();
|
259
299
|
r.isNew = true; // to distinguish new records
|
@@ -270,7 +310,7 @@
|
|
270
310
|
},
|
271
311
|
|
272
312
|
onDel: function() {
|
273
|
-
Ext.Msg.confirm(this.i18n.
|
313
|
+
Ext.Msg.confirm(this.i18n.confirmation, this.i18n.areYouSure, function(btn){
|
274
314
|
if (btn == 'yes') {
|
275
315
|
var records = [];
|
276
316
|
this.getSelectionModel().each(function(r){
|
@@ -492,14 +532,26 @@
|
|
492
532
|
},
|
493
533
|
|
494
534
|
defaultGetRowClass: function(r){
|
495
|
-
|
496
|
-
|
535
|
+
return r.isNew ? "grid-dirty-record" : ""
|
536
|
+
},
|
497
537
|
|
498
538
|
selectFirstRow: function(){
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
539
|
+
this.getSelectionModel().suspendEvents();
|
540
|
+
this.getSelectionModel().selectRow(0);
|
541
|
+
this.getSelectionModel().resumeEvents();
|
542
|
+
},
|
543
|
+
|
544
|
+
setDefaultColumnType: function(c) {
|
545
|
+
if (c.xtype || c.renderer) return;
|
546
|
+
|
547
|
+
switch (c.attrType) {
|
548
|
+
case 'datetime': {
|
549
|
+
c.xtype = 'datecolumn';
|
550
|
+
c.format = c.format || "Y-m-d g:i:s";
|
551
|
+
break;
|
552
|
+
}
|
553
|
+
}
|
554
|
+
},
|
503
555
|
|
504
556
|
// Normalizes the renderer for a column.
|
505
557
|
// Renderer may be:
|
@@ -517,34 +569,56 @@
|
|
517
569
|
// * ["Some.scope.Format.customRenderer", 10, 20, 30] (if Some.scope.Format.customRenderer is a function)
|
518
570
|
// * "function(v){ return 'Value: ' + v; }"
|
519
571
|
normalizeRenderer: function(c) {
|
520
|
-
|
572
|
+
if (!c.renderer) return;
|
521
573
|
|
522
|
-
|
574
|
+
var name, args = [];
|
523
575
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
576
|
+
if ('string' === typeof c.renderer) {
|
577
|
+
name = c.renderer;
|
578
|
+
} else {
|
579
|
+
name = c.renderer[0];
|
580
|
+
args = c.renderer.slice(1);
|
581
|
+
}
|
582
|
+
|
583
|
+
// First check whether Ext.util.Format has it
|
584
|
+
if (Ext.isFunction(Ext.util.Format[name])) {
|
585
|
+
c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
|
586
|
+
} else if (Ext.isFunction(this[name])) {
|
587
|
+
// ... then if our own class has it
|
588
|
+
c.renderer = this[name].createDelegate(this, args, 1);
|
589
|
+
} else {
|
590
|
+
// ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
|
591
|
+
eval("c.renderer = " + c.renderer + ";");
|
592
|
+
}
|
593
|
+
},
|
530
594
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
c.renderer = this[name].createDelegate(this, args, 1);
|
595
|
+
normalizeEditor: function(c) {
|
596
|
+
if (c.assoc) {
|
597
|
+
} else {
|
598
|
+
if (c.editor) {
|
599
|
+
c.editor = Ext.isObject(c.editor) ? c.editor : {xtype:c.editor};
|
537
600
|
} else {
|
538
|
-
|
539
|
-
eval("c.renderer = " + c.renderer + ";");
|
601
|
+
c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
|
540
602
|
}
|
541
|
-
}
|
603
|
+
}
|
604
|
+
|
605
|
+
},
|
542
606
|
|
543
607
|
onEdit: function(){
|
544
608
|
var row = this.getSelectionModel().getSelected();
|
545
609
|
if (row){
|
546
610
|
this.tryStartEditing(this.store.indexOf(row));
|
547
611
|
}
|
612
|
+
},
|
613
|
+
|
614
|
+
// Not a very clean approach to clean-up. The problem is that this way the advanced search functionality stops being really pluggable. With Ext JS 4 find the way to make it truely so.
|
615
|
+
onDestroy: function(){
|
616
|
+
Netzke.classes.Basepack.GridPanel.superclass.onDestroy.call(this);
|
617
|
+
|
618
|
+
// Destroy the search window (here's the problem: we are not supposed to know it exists)
|
619
|
+
if (this.searchWindow) {
|
620
|
+
this.searchWindow.destroy();
|
621
|
+
}
|
548
622
|
}
|
549
623
|
|
550
624
|
// :reorder_columns => <<-END_OF_JAVASCRIPT.l,
|