skozlov-netzke-basepack 0.1.1.2 → 0.5.0
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.
- data/.autotest +1 -0
- data/.gitignore +5 -0
- data/LICENSE +2 -19
- data/README.rdoc +87 -0
- data/Rakefile +28 -12
- data/TODO.rdoc +7 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +3 -0
- data/init.rb +0 -1
- data/javascripts/basepack.js +839 -49
- data/lib/app/models/netzke_auto_column.rb +56 -0
- data/lib/netzke/accordion_panel.rb +113 -0
- data/lib/netzke/active_record/basepack.rb +104 -0
- data/lib/netzke/active_record/data_accessor.rb +21 -0
- data/lib/netzke/basic_app.rb +325 -0
- data/lib/netzke/border_layout_panel.rb +128 -0
- data/lib/netzke/configuration_panel.rb +24 -0
- data/lib/netzke/data_accessor.rb +71 -0
- data/lib/netzke/ext.rb +6 -0
- data/lib/netzke/field_model.rb +131 -0
- data/lib/netzke/fields_configurator.rb +95 -0
- data/lib/netzke/form_panel.rb +214 -0
- data/lib/netzke/form_panel_api.rb +74 -0
- data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
- data/lib/netzke/form_panel_js.rb +129 -0
- data/lib/netzke/grid_panel.rb +442 -0
- data/lib/netzke/grid_panel_api.rb +352 -0
- data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
- data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
- data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
- data/lib/netzke/grid_panel_js.rb +721 -0
- data/lib/netzke/panel.rb +13 -0
- data/lib/netzke/plugins/configuration_tool.rb +121 -0
- data/lib/netzke/property_editor.rb +105 -0
- data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
- data/lib/netzke/search_panel.rb +62 -0
- data/lib/netzke/tab_panel.rb +160 -0
- data/lib/netzke/table_editor.rb +118 -0
- data/lib/netzke/tree_panel.rb +73 -0
- data/lib/netzke/wrapper.rb +42 -0
- data/lib/netzke-basepack.rb +9 -15
- data/netzke-basepack.gemspec +147 -20
- data/stylesheets/basepack.css +26 -0
- data/test/app_root/app/models/book.rb +1 -1
- data/test/app_root/config/environment.rb +1 -0
- data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
- data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
- data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
- data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
- data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
- data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
- data/test/test_helper.rb +1 -2
- data/test/unit/accordion_panel_test.rb +20 -0
- data/test/unit/active_record_basepack_test.rb +54 -0
- data/test/unit/grid_panel_test.rb +43 -0
- data/test/unit/helper_model_test.rb +30 -0
- data/test/unit/netzke_basepack_test.rb +4 -0
- data/test/unit/tab_panel_test.rb +21 -0
- metadata +96 -72
- data/CHANGELOG +0 -14
- data/Manifest +0 -65
- data/README.mdown +0 -18
- data/generators/netzke_basepack/USAGE +0 -8
- data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
- data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
- data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
- data/lib/app/models/netzke_grid_column.rb +0 -23
- data/lib/netzke/accordion.rb +0 -11
- data/lib/netzke/ar_ext.rb +0 -163
- data/lib/netzke/column.rb +0 -43
- data/lib/netzke/container.rb +0 -81
- data/lib/netzke/grid.rb +0 -120
- data/lib/netzke/grid_interface.rb +0 -156
- data/lib/netzke/grid_js_builder.rb +0 -276
- data/lib/netzke/preference_grid.rb +0 -43
- data/lib/netzke/properties_tool.rb +0 -66
- data/lib/netzke/property_grid.rb +0 -60
- data/test/ar_ext_test.rb +0 -39
- data/test/column_test.rb +0 -27
- data/test/grid_test.rb +0 -43
- data/test/netzke_basepack_test.rb +0 -8
@@ -0,0 +1,442 @@
|
|
1
|
+
require 'searchlogic'
|
2
|
+
|
3
|
+
module Netzke
|
4
|
+
# == GridPanel
|
5
|
+
# Ext.grid.EditorGridPanel + server-side code
|
6
|
+
#
|
7
|
+
# == Features:
|
8
|
+
# * multi-line CRUD operations - get, post, delete, create
|
9
|
+
# * (multe-record) editing and adding records through a form
|
10
|
+
# * column resize, move and hide
|
11
|
+
# * permissions
|
12
|
+
# * sorting
|
13
|
+
# * pagination
|
14
|
+
# * filtering
|
15
|
+
# * extended configurable search
|
16
|
+
# * rows reordering (drag-n-drop)
|
17
|
+
# * dynamic configuration of properties and columns
|
18
|
+
#
|
19
|
+
# == Class configuration
|
20
|
+
# Configuration on this level is effective during the life-time of the application. They can be put into a .rb file
|
21
|
+
# inside of config/initializers like this:
|
22
|
+
#
|
23
|
+
# Netzke::GridPanel.configure :column_filters_available, false
|
24
|
+
# Netzke::GridPanel.configure :default_config => {:ext_config => {:enable_config_tool => false}}
|
25
|
+
#
|
26
|
+
# Most of these options directly influence the amount of JavaScript code that is generated for this widget's class.
|
27
|
+
# The less functionality is enabled, the less code is generated.
|
28
|
+
#
|
29
|
+
# The following configuration options are available:
|
30
|
+
# * <tt>:column_filters_available</tt> - (default is true) include code for the filters in the column's context menu
|
31
|
+
# * <tt>:config_tool_available</tt> - (default is true) include code for the configuration tool that launches the configuration panel
|
32
|
+
# * <tt>:edit_in_form_available</tt> - (defaults to true) include code for (multi-record) editing and adding records through a form
|
33
|
+
# * <tt>:extended_search_available</tt> - (defaults to true) include code for extended configurable search
|
34
|
+
# * <tt>:default_config</tt> - a hash of default configuration options for each instance of the GridPanel widget.
|
35
|
+
# See the "Instance configuration" section below.
|
36
|
+
#
|
37
|
+
# == Instance configuration
|
38
|
+
# The following config options are available:
|
39
|
+
# * <tt>:data_class_name</tt> - name of the ActiveRecord model that provides data to this GridPanel.
|
40
|
+
# * <tt>:strong_default_attrs</tt> - a hash of attributes to be merged atop of every created/updated record.
|
41
|
+
# * <tt>:scopes</tt> - an array of searchlogic-compatible scopes to filter grid data like this:
|
42
|
+
#
|
43
|
+
# ["user_id_not", 100]
|
44
|
+
#
|
45
|
+
# In the <tt>:ext_config</tt> hash (see Netzke::Base) the following GridPanel specific options are available:
|
46
|
+
#
|
47
|
+
# * <tt>:enable_column_filters</tt> - enable filters in column's context menu
|
48
|
+
# * <tt>:enable_edit_in_form</tt> - provide buttons into the toolbar that activate editing/adding records via a form
|
49
|
+
# * <tt>:enable_extended_search</tt> - provide a button into the toolbar that shows configurable search form
|
50
|
+
# * <tt>:enable_context_menu</tt> - enable rows context menu
|
51
|
+
# * <tt>:enable_rows_reordering</tt> - enable reordering of rows with drag-n-drop; underlying model (specified in <tt>:data_class_name</tt>) must implement "acts_as_list"-compatible functionality; defaults to <tt>false</tt>
|
52
|
+
# * <tt>:enable_pagination</tt> - enable pagination; defaults to <tt>true</tt>
|
53
|
+
# * <tt>:rows_per_page</tt> - number of rows per page (ignored when <tt>:enable_pagination</tt> is set to <tt>false</tt>)
|
54
|
+
# * <tt>:load_inline_data</tt> - load initial data into the grid right after its instantiation (saves a request to server); defaults to <tt>true</tt>
|
55
|
+
# * <tt>:mode</tt> - when set to <tt>:config</tt>, GridPanel loads in configuration mode
|
56
|
+
#
|
57
|
+
# Additionally supports Netzke::Base config options.
|
58
|
+
class GridPanel < Base
|
59
|
+
# javascript (client-side)
|
60
|
+
include Netzke::GridPanelJs
|
61
|
+
# API (server-side)
|
62
|
+
include Netzke::GridPanelApi
|
63
|
+
# Code shared between GridPanel, FormPanel, and other widgets that serve as interface to database tables
|
64
|
+
include Netzke::DataAccessor
|
65
|
+
|
66
|
+
def self.enforce_config_consistency
|
67
|
+
config[:default_config][:ext_config][:enable_edit_in_form] &&= config[:edit_in_form_available]
|
68
|
+
config[:default_config][:ext_config][:enable_extended_search] &&= config[:extended_search_available]
|
69
|
+
config[:default_config][:ext_config][:enable_rows_reordering] &&= config[:rows_reordering_available]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Class-level configuration. This options directly influence the amount of generated
|
73
|
+
# javascript code for this widget's class. For example, if you don't want filters for the grid,
|
74
|
+
# set :column_filters_available to false, and the javascript for the filters won't be included at all.
|
75
|
+
def self.config
|
76
|
+
set_default_config({
|
77
|
+
|
78
|
+
:column_filters_available => true,
|
79
|
+
:config_tool_available => true,
|
80
|
+
:edit_in_form_available => true,
|
81
|
+
:extended_search_available => true,
|
82
|
+
:rows_reordering_available => false,
|
83
|
+
|
84
|
+
:default_config => {
|
85
|
+
:ext_config => {
|
86
|
+
:enable_edit_in_form => true,
|
87
|
+
:enable_extended_search => true,
|
88
|
+
:enable_column_filters => true,
|
89
|
+
:load_inline_data => true,
|
90
|
+
:enable_context_menu => true,
|
91
|
+
|
92
|
+
:enable_pagination => true,
|
93
|
+
:rows_per_page => 25,
|
94
|
+
|
95
|
+
:mode => :normal, # when set to :config, :configuration button is enabled
|
96
|
+
|
97
|
+
:enable_rows_reordering => false, # drag n drop
|
98
|
+
|
99
|
+
:tools => %w{ refresh }
|
100
|
+
},
|
101
|
+
|
102
|
+
:persistent_config => false
|
103
|
+
}
|
104
|
+
})
|
105
|
+
end
|
106
|
+
|
107
|
+
# Include extra javascript that we depend on
|
108
|
+
def self.include_js
|
109
|
+
res = []
|
110
|
+
|
111
|
+
# Checkcolumn
|
112
|
+
res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/check-column.js"
|
113
|
+
|
114
|
+
# Filters
|
115
|
+
if config[:column_filters_available]
|
116
|
+
ext_examples = Netzke::Base.config[:ext_location] + "/examples/"
|
117
|
+
res << ext_examples + "grid-filtering/menu/EditableItem.js"
|
118
|
+
res << ext_examples + "grid-filtering/menu/RangeMenu.js"
|
119
|
+
res << ext_examples + "grid-filtering/grid/GridFilters.js"
|
120
|
+
|
121
|
+
%w{Boolean Date List Numeric String}.unshift("").each do |f|
|
122
|
+
res << ext_examples + "grid-filtering/grid/filter/#{f}Filter.js"
|
123
|
+
end
|
124
|
+
|
125
|
+
res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/filters.js"
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
# DD
|
130
|
+
if config[:rows_reordering_available]
|
131
|
+
res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/rows-dd.js"
|
132
|
+
end
|
133
|
+
|
134
|
+
res
|
135
|
+
end
|
136
|
+
|
137
|
+
# Define connection points between client side and server side of GridPanel.
|
138
|
+
# See implementation of equally named methods in the GridPanelApi module.
|
139
|
+
api :get_data, :post_data, :delete_data, :resize_column, :move_column, :hide_column, :get_combobox_options, :move_rows
|
140
|
+
|
141
|
+
# Edit in form
|
142
|
+
api :create_new_record if config[:edit_in_form_available]
|
143
|
+
|
144
|
+
def data_class
|
145
|
+
@data_class ||= config[:data_class_name].nil? ? raise(ArgumentError, "No data_class_name specified for widget #{id_name}") : config[:data_class_name].constantize
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def initialize(config = {}, parent = nil)
|
150
|
+
super
|
151
|
+
|
152
|
+
apply_helpers
|
153
|
+
end
|
154
|
+
|
155
|
+
# Columns to be displayed by the FieldConfigurator.
|
156
|
+
def self.config_columns
|
157
|
+
[
|
158
|
+
{:name => :name, :type => :string, :editor => :combobox, :width => 200},
|
159
|
+
{:name => :excluded, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Excl"},
|
160
|
+
{:name => :value},
|
161
|
+
{:name => :header},
|
162
|
+
{:name => :hidden, :type => :boolean, :editor => :checkbox},
|
163
|
+
{:name => :read_only, :type => :boolean, :editor => :checkbox, :header => "R"},
|
164
|
+
{:name => :editor, :type => :string, :editor => {:xtype => :combobox, :options => Netzke::Ext::FORM_FIELD_XTYPES}},
|
165
|
+
{:name => :renderer, :type => :string},
|
166
|
+
# {:name => :renderer, :type => :string, :editor => {:xtype => :jsonfield}},
|
167
|
+
{:name => :with_filters, :type => :boolean, :editor => :checkbox, :default => true, :header => "Filters"},
|
168
|
+
|
169
|
+
# some rarely used configurations, hidden
|
170
|
+
{:name => :width, :type => :integer, :editor => :numberfield, :hidden => true},
|
171
|
+
{:name => :hideable, :type => :boolean, :editor => :checkbox, :default => true, :hidden => true},
|
172
|
+
{:name => :sortable, :type => :boolean, :editor => :checkbox, :default => true, :hidden => true},
|
173
|
+
]
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.property_fields
|
177
|
+
res = [
|
178
|
+
{:name => :ext_config__title, :type => :string},
|
179
|
+
{:name => :ext_config__header, :type => :boolean, :default => true},
|
180
|
+
{:name => :ext_config__enable_context_menu, :type => :boolean, :default => true},
|
181
|
+
{:name => :ext_config__context_menu, :type => :json},
|
182
|
+
{:name => :ext_config__enable_pagination, :type => :boolean, :default => true},
|
183
|
+
{:name => :ext_config__rows_per_page, :type => :integer},
|
184
|
+
# {:name => :ext_config__bbar, :type => :json},
|
185
|
+
{:name => :ext_config__prohibit_create, :type => :boolean},
|
186
|
+
{:name => :ext_config__prohibit_update, :type => :boolean},
|
187
|
+
{:name => :ext_config__prohibit_delete, :type => :boolean},
|
188
|
+
{:name => :ext_config__prohibit_read, :type => :boolean}
|
189
|
+
]
|
190
|
+
|
191
|
+
res << {:name => :ext_config__enable_extended_search, :type => :boolean} if config[:extended_search_available]
|
192
|
+
res << {:name => :ext_config__enable_edit_in_form, :type => :boolean} if config[:edit_in_form_available]
|
193
|
+
|
194
|
+
res
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
def independent_config
|
199
|
+
res = super
|
200
|
+
|
201
|
+
# Bottom bar
|
202
|
+
if res[:ext_config][:bbar].nil?
|
203
|
+
res[:ext_config][:bbar] = %w{ add edit apply del }
|
204
|
+
res[:ext_config][:bbar] << "-" << "add_in_form" << "edit_in_form" if res[:ext_config][:enable_edit_in_form]
|
205
|
+
res[:ext_config][:bbar] << "-" << "search" if res[:ext_config][:enable_extended_search]
|
206
|
+
end
|
207
|
+
|
208
|
+
# Context menu
|
209
|
+
res[:ext_config][:context_menu] ||= default_context_menu(res)
|
210
|
+
|
211
|
+
res
|
212
|
+
end
|
213
|
+
|
214
|
+
def default_context_menu(indep_config)
|
215
|
+
res = %w{ edit del }
|
216
|
+
res << "-" << "edit_in_form" if indep_config[:ext_config][:enable_edit_in_form]
|
217
|
+
res
|
218
|
+
end
|
219
|
+
|
220
|
+
def configuration_widgets
|
221
|
+
res = []
|
222
|
+
res << {
|
223
|
+
:persistent_config => true,
|
224
|
+
:name => 'columns',
|
225
|
+
:widget_class_name => "FieldsConfigurator",
|
226
|
+
:active => true,
|
227
|
+
:widget => self
|
228
|
+
}
|
229
|
+
res << {
|
230
|
+
:name => 'general',
|
231
|
+
:widget_class_name => "PropertyEditor",
|
232
|
+
:widget => self,
|
233
|
+
:ext_config => {:title => false}
|
234
|
+
}
|
235
|
+
res
|
236
|
+
end
|
237
|
+
|
238
|
+
def actions
|
239
|
+
# Defaults
|
240
|
+
res = {
|
241
|
+
:add => {:text => 'Add', :disabled => ext_config[:prohibit_create]},
|
242
|
+
:edit => {:text => 'Edit', :disabled => true},
|
243
|
+
:del => {:text => 'Delete', :disabled => true},
|
244
|
+
:apply => {:text => 'Apply', :disabled => ext_config[:prohibit_update] && ext_config[:prohibit_create]}
|
245
|
+
}
|
246
|
+
|
247
|
+
# Edit in form
|
248
|
+
res.merge!({
|
249
|
+
:add_in_form => {:text => 'Add in form'},
|
250
|
+
:edit_in_form => {:text => 'Edit in form', :disabled => true}
|
251
|
+
}) if ext_config[:enable_edit_in_form]
|
252
|
+
|
253
|
+
# Extended search
|
254
|
+
res.merge!({
|
255
|
+
:search => {:text => 'Search'}
|
256
|
+
}) if ext_config[:enable_extended_search]
|
257
|
+
|
258
|
+
res
|
259
|
+
end
|
260
|
+
|
261
|
+
def initial_late_aggregatees
|
262
|
+
res = {}
|
263
|
+
|
264
|
+
# Edit in form
|
265
|
+
res.merge!({
|
266
|
+
:edit_form => {
|
267
|
+
:widget_class_name => "FormPanel",
|
268
|
+
:persistent_config => true,
|
269
|
+
:data_class_name => config[:data_class_name],
|
270
|
+
:ext_config => {
|
271
|
+
:bbar => false,
|
272
|
+
:header => false,
|
273
|
+
:mode => ext_config[:mode]
|
274
|
+
}
|
275
|
+
},
|
276
|
+
|
277
|
+
:multi_edit_form => {
|
278
|
+
:widget_class_name => "FormPanel",
|
279
|
+
:persistent_config => true,
|
280
|
+
:data_class_name => config[:data_class_name],
|
281
|
+
:ext_config => {
|
282
|
+
:bbar => false,
|
283
|
+
:header => false,
|
284
|
+
:mode => ext_config[:mode]
|
285
|
+
}
|
286
|
+
},
|
287
|
+
|
288
|
+
:new_record_form => {
|
289
|
+
:widget_class_name => "FormPanel",
|
290
|
+
:persistent_config => true,
|
291
|
+
:data_class_name => config[:data_class_name],
|
292
|
+
:ext_config => {
|
293
|
+
:bbar => false,
|
294
|
+
:header => false,
|
295
|
+
:mode => ext_config[:mode]
|
296
|
+
},
|
297
|
+
:record => config[:data_class_name].constantize.new
|
298
|
+
}
|
299
|
+
}) if ext_config[:enable_edit_in_form]
|
300
|
+
|
301
|
+
# Extended search
|
302
|
+
res.merge!({
|
303
|
+
:search_panel => {
|
304
|
+
:widget_class_name => "SearchPanel",
|
305
|
+
:search_class_name => config[:data_class_name],
|
306
|
+
:persistent_config => true,
|
307
|
+
:ext_config => {
|
308
|
+
:header => false,
|
309
|
+
:bbar => false,
|
310
|
+
:mode => ext_config[:mode]
|
311
|
+
},
|
312
|
+
}
|
313
|
+
}) if ext_config[:enable_extended_search]
|
314
|
+
|
315
|
+
res
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
include Plugins::ConfigurationTool if config[:config_tool_available] # it will load ConfigurationPanel into a modal window
|
320
|
+
|
321
|
+
def columns
|
322
|
+
@columns ||= get_columns
|
323
|
+
end
|
324
|
+
|
325
|
+
# Normalized columns
|
326
|
+
def normalized_columns
|
327
|
+
@normalized_columns ||= normalize_columns(columns)
|
328
|
+
end
|
329
|
+
|
330
|
+
def get_columns
|
331
|
+
if config[:persistent_config]
|
332
|
+
persistent_config['layout__columns'] ||= default_columns
|
333
|
+
res = normalize_array_of_columns(persistent_config['layout__columns'])
|
334
|
+
else
|
335
|
+
res = default_columns
|
336
|
+
end
|
337
|
+
|
338
|
+
# denormalize
|
339
|
+
res.map{ |c| c.is_a?(Hash) && c.reject{ |k,v| k == :name }.empty? ? c[:name].to_sym : c }
|
340
|
+
end
|
341
|
+
|
342
|
+
# Normalizes the column at position +index+ and returns it.
|
343
|
+
def column_at(index)
|
344
|
+
if columns[index].is_a?(Hash)
|
345
|
+
columns[index]
|
346
|
+
else
|
347
|
+
column_name = columns.delete_at(index)
|
348
|
+
normalized_column = normalize_column(column_name)
|
349
|
+
columns.insert(index, normalized_column)
|
350
|
+
normalized_column
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Stores modified columns in persistent storage
|
355
|
+
def save_columns!
|
356
|
+
persistent_config[:layout__columns] = columns
|
357
|
+
end
|
358
|
+
|
359
|
+
TYPE_EDITOR_MAP = {
|
360
|
+
:integer => :numberfield,
|
361
|
+
:boolean => :checkbox,
|
362
|
+
:date => :datefield,
|
363
|
+
:datetime => :xdatetime,
|
364
|
+
:text => :textarea
|
365
|
+
# :string => :textfield
|
366
|
+
}
|
367
|
+
|
368
|
+
def default_columns
|
369
|
+
# columns specified in widget's config
|
370
|
+
columns_from_config = config[:columns] && normalize_columns(config[:columns])
|
371
|
+
|
372
|
+
if columns_from_config
|
373
|
+
# reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
|
374
|
+
for c in columns_from_config
|
375
|
+
corresponding_exposed_column = predefined_columns.find{ |k| k[:name] == c[:name] }
|
376
|
+
c.reverse_merge!(corresponding_exposed_column) if corresponding_exposed_column
|
377
|
+
end
|
378
|
+
columns_for_create = columns_from_config
|
379
|
+
else
|
380
|
+
# we didn't have columns configured in widget's config, so, use the columns from the data class
|
381
|
+
columns_for_create = predefined_columns
|
382
|
+
end
|
383
|
+
|
384
|
+
columns_for_create.map! do |c|
|
385
|
+
# detect ActiveRecord column type (if the column is "real") or fall back to :virtual
|
386
|
+
type = (data_class.columns_hash[c[:name].to_s] && data_class.columns_hash[c[:name].to_s].type) || :virtual
|
387
|
+
|
388
|
+
# detect :assoc__method
|
389
|
+
if c[:name].to_s.index('__')
|
390
|
+
assoc_name, method = c[:name].to_s.split('__').map(&:to_sym)
|
391
|
+
if assoc = data_class.reflect_on_association(assoc_name)
|
392
|
+
assoc_column = assoc.klass.columns_hash[method.to_s]
|
393
|
+
assoc_method_type = assoc_column.try(:type)
|
394
|
+
if assoc_method_type
|
395
|
+
c[:editor] ||= TYPE_EDITOR_MAP[assoc_method_type] == :checkbox ? :checkbox : :combobox
|
396
|
+
end
|
397
|
+
type = :association
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# detect association column (e.g. :category_id)
|
402
|
+
if assoc = data_class.reflect_on_all_associations.detect{|a| a.primary_key_name.to_sym == c[:name]}
|
403
|
+
c[:editor] ||= :combobox
|
404
|
+
assoc_method = %w{name title label id}.detect{|m| (assoc.klass.instance_methods + assoc.klass.column_names).include?(m) } || assoc.klass.primary_key
|
405
|
+
c[:name] = "#{assoc.name}__#{assoc_method}".to_sym
|
406
|
+
type = :association
|
407
|
+
end
|
408
|
+
|
409
|
+
# Some smart defaults
|
410
|
+
|
411
|
+
# default editor, dependent on column type
|
412
|
+
c[:editor] ||= TYPE_EDITOR_MAP[type] unless TYPE_EDITOR_MAP[type].nil?
|
413
|
+
|
414
|
+
# narrow column for checkbox
|
415
|
+
c[:width] ||= 50 if c[:editor] == :checkbox
|
416
|
+
|
417
|
+
# wider column for xdatetime
|
418
|
+
c[:width] ||= 120 if c[:editor] == :xdatetime
|
419
|
+
|
420
|
+
# hide ID column
|
421
|
+
c[:hidden] = true if c[:name] == data_class.primary_key.to_sym && c[:hidden].nil?
|
422
|
+
|
423
|
+
# Some default limitations for virtual columns
|
424
|
+
if type == :virtual
|
425
|
+
# disable filters
|
426
|
+
c[:with_filters].nil? && c[:with_filters] = false
|
427
|
+
# disable sorting
|
428
|
+
c[:sortable].nil? && c[:sortable] = false
|
429
|
+
# read-only
|
430
|
+
c[:read_only].nil? && c[:read_only] = true
|
431
|
+
end
|
432
|
+
|
433
|
+
# denormalize column (save space)
|
434
|
+
c.reject{ |k,v| k == :name }.empty? ? c[:name] : c
|
435
|
+
end
|
436
|
+
|
437
|
+
columns_for_create
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
@@ -0,0 +1,352 @@
|
|
1
|
+
module Netzke
|
2
|
+
module GridPanelApi
|
3
|
+
def post_data(params)
|
4
|
+
success = true
|
5
|
+
mod_records = {}
|
6
|
+
[:create, :update].each do |operation|
|
7
|
+
data = ActiveSupport::JSON.decode(params["#{operation}d_records"]) if params["#{operation}d_records"]
|
8
|
+
if !data.nil? && !data.empty? # data may be nil for one of the operations
|
9
|
+
mod_records[operation] = process_data(data, operation)
|
10
|
+
mod_records[operation] = nil if mod_records[operation].empty?
|
11
|
+
end
|
12
|
+
break if !success
|
13
|
+
end
|
14
|
+
{
|
15
|
+
:update_new_records => mod_records[:create],
|
16
|
+
:update_mod_records => mod_records[:update] || {},
|
17
|
+
:feedback => @flash
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_data(params = {})
|
22
|
+
if !ext_config[:prohibit_read]
|
23
|
+
records = get_records(params)
|
24
|
+
{:data => records.map{|r| r.to_array(normalized_columns)}, :total => ext_config[:enable_pagination] && records.total_entries}
|
25
|
+
else
|
26
|
+
flash :error => "You don't have permissions to read data"
|
27
|
+
{:feedback => @flash}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_data(params = {})
|
32
|
+
if !ext_config[:prohibit_delete]
|
33
|
+
record_ids = ActiveSupport::JSON.decode(params[:records])
|
34
|
+
klass = config[:data_class_name].constantize
|
35
|
+
klass.delete(record_ids)
|
36
|
+
{:feedback => "Deleted #{record_ids.size} record(s)", :load_store_data => get_data}
|
37
|
+
else
|
38
|
+
{:feedback => "You don't have permissions to delete data"}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def resize_column(params)
|
43
|
+
raise "Called api_resize_column while not configured to do so" if ext_config[:enable_column_resize] == false
|
44
|
+
column_at(params[:index].to_i)[:width] = params[:size].to_i
|
45
|
+
save_columns!
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
|
49
|
+
def hide_column(params)
|
50
|
+
raise "Called api_hide_column while not configured to do so" if ext_config[:enable_column_hide] == false
|
51
|
+
column_at(params[:index].to_i)[:hidden] = params[:hidden].to_b
|
52
|
+
save_columns!
|
53
|
+
{}
|
54
|
+
end
|
55
|
+
|
56
|
+
def move_column(params)
|
57
|
+
raise "Called api_move_column while not configured to do so" if ext_config[:enable_column_move] == false
|
58
|
+
column_to_move = columns.delete_at(params[:old_index].to_i)
|
59
|
+
columns.insert(params[:new_index].to_i, column_to_move)
|
60
|
+
save_columns!
|
61
|
+
|
62
|
+
# reorder the columns on the client side (still not sure if it's not an overkill)
|
63
|
+
# {:reorder_columns => columns.map(&:name)} # Well, I think it IS an overkill - commented out
|
64
|
+
# until proven to be necessary
|
65
|
+
{}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the choices for the column
|
69
|
+
def get_combobox_options(params)
|
70
|
+
column = params[:column]
|
71
|
+
query = params[:query]
|
72
|
+
|
73
|
+
{:data => config[:data_class_name].constantize.options_for(column, query).map{|s| [s]}}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns searchlogic's search with all the conditions
|
77
|
+
def get_search(params)
|
78
|
+
@search ||= begin
|
79
|
+
raise ArgumentError, "No data_class_name specified for widget '#{name}'" if !config[:data_class_name]
|
80
|
+
|
81
|
+
# make params coming from Ext grid filters understandable by searchlogic
|
82
|
+
search_params = normalize_params(params)
|
83
|
+
|
84
|
+
# merge with conditions coming from the config
|
85
|
+
search_params[:conditions].deep_merge!(config[:conditions] || {})
|
86
|
+
|
87
|
+
# merge with extra conditions (in searchlogic format, come from the extended search form)
|
88
|
+
search_params[:conditions].deep_merge!(
|
89
|
+
normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
|
90
|
+
) if params[:extra_conditions]
|
91
|
+
|
92
|
+
search = config[:data_class_name].constantize.search(search_params)
|
93
|
+
|
94
|
+
# applying scopes
|
95
|
+
scopes.each do |s|
|
96
|
+
if s.is_a?(Array)
|
97
|
+
scope_name, *args = s
|
98
|
+
search.send(scope_name, *args)
|
99
|
+
else
|
100
|
+
search.send(s)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
search
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def configuration_panel__columns__get_combobox_options(params)
|
109
|
+
query = params[:query]
|
110
|
+
|
111
|
+
data_arry = case params[:column]
|
112
|
+
when "name"
|
113
|
+
predefined_columns.map{ |c| c[:name].to_s }
|
114
|
+
else
|
115
|
+
raise RuntimeError, "Don't know about options for column '#{params[:column]}'"
|
116
|
+
end
|
117
|
+
|
118
|
+
{:data => data_arry.grep(/^#{query}/).map{ |n| [n] }}.to_nifty_json
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
# operation => :update || :create
|
124
|
+
def process_data(data, operation)
|
125
|
+
success = true
|
126
|
+
# mod_record_ids = []
|
127
|
+
mod_records = {}
|
128
|
+
if !ext_config["prohibit_#{operation}".to_sym]
|
129
|
+
klass = config[:data_class_name].constantize
|
130
|
+
modified_records = 0
|
131
|
+
data.each do |record_hash|
|
132
|
+
id = record_hash.delete('id')
|
133
|
+
record = operation == :create ? klass.new : klass.find(id)
|
134
|
+
success = true
|
135
|
+
|
136
|
+
# merge with strong default attirbutes
|
137
|
+
record_hash.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
|
138
|
+
|
139
|
+
# process all attirubutes for this record
|
140
|
+
record_hash.each_pair do |k,v|
|
141
|
+
begin
|
142
|
+
record.send("#{k}=",v)
|
143
|
+
rescue ArgumentError => exc
|
144
|
+
flash :error => exc.message
|
145
|
+
success = false
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# try to save
|
151
|
+
# modified_records += 1 if success && record.save
|
152
|
+
mod_records[id] = record.to_array(columns) if success && record.save
|
153
|
+
# mod_record_ids << id if success && record.save
|
154
|
+
|
155
|
+
# flash eventual errors
|
156
|
+
if !record.errors.empty?
|
157
|
+
success = false
|
158
|
+
record.errors.each_full do |msg|
|
159
|
+
flash :error => msg
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
# flash :notice => "#{operation.to_s.capitalize}d #{modified_records} record(s)"
|
164
|
+
else
|
165
|
+
success = false
|
166
|
+
flash :error => "You don't have permissions to #{operation} data"
|
167
|
+
end
|
168
|
+
mod_records
|
169
|
+
end
|
170
|
+
|
171
|
+
# get records
|
172
|
+
def get_records(params)
|
173
|
+
# Restore params from widget_session if requested
|
174
|
+
if params[:with_last_params]
|
175
|
+
params = widget_session[:last_params]
|
176
|
+
else
|
177
|
+
# remember the last params
|
178
|
+
widget_session[:last_params] = params
|
179
|
+
end
|
180
|
+
|
181
|
+
search = get_search(params)
|
182
|
+
|
183
|
+
# sorting
|
184
|
+
if params[:sort]
|
185
|
+
assoc, method = params[:sort].split('__')
|
186
|
+
sort_string = method.nil? ? assoc : "#{assoc}_#{method}"
|
187
|
+
sort_string = (params[:dir] == "ASC" ? "ascend_by_" : "descend_by_") + sort_string
|
188
|
+
search.order(sort_string)
|
189
|
+
end
|
190
|
+
|
191
|
+
# pagination
|
192
|
+
if ext_config[:enable_pagination]
|
193
|
+
per_page = ext_config[:rows_per_page]
|
194
|
+
page = params[:limit] ? params[:start].to_i/params[:limit].to_i + 1 : 1
|
195
|
+
search.paginate(:per_page => per_page, :page => page)
|
196
|
+
else
|
197
|
+
search.all
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Create record with form
|
202
|
+
def create_new_record(params)
|
203
|
+
form_data = ActiveSupport::JSON.decode(params[:data])
|
204
|
+
res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
|
205
|
+
|
206
|
+
if res[:set_form_values]
|
207
|
+
# successful creation
|
208
|
+
res[:set_form_values] = nil
|
209
|
+
res[:on_successfull_record_creation] = true
|
210
|
+
end
|
211
|
+
res
|
212
|
+
end
|
213
|
+
|
214
|
+
# Move rows
|
215
|
+
def move_rows(params)
|
216
|
+
if defined?(ActsAsList) && data_class.ancestors.include?(ActsAsList::InstanceMethods)
|
217
|
+
ids = JSON.parse(params[:ids]).reverse
|
218
|
+
ids.each_with_index do |id, i|
|
219
|
+
r = data_class.find(id)
|
220
|
+
r.insert_at(params[:new_index].to_i + i + 1)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
raise RuntimeError, "Data class should 'acts_as_list' to support moving rows"
|
224
|
+
end
|
225
|
+
{}
|
226
|
+
end
|
227
|
+
|
228
|
+
# When providing the edit_form aggregatee, fill in the form with the requested record
|
229
|
+
def load_aggregatee_with_cache(params)
|
230
|
+
if params[:id] == 'edit_form'
|
231
|
+
super(params.merge(:config => {:record_id => params[:record_id]}.to_json))
|
232
|
+
else
|
233
|
+
super
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Search scopes, in searchlogic format
|
238
|
+
def scopes
|
239
|
+
@scopes ||= config[:scopes] || []
|
240
|
+
end
|
241
|
+
|
242
|
+
# Converts Ext.grid.GridFilters filters to searchlogic conditions, e.g.
|
243
|
+
# {"0" => {
|
244
|
+
# "data" => {
|
245
|
+
# "type" => "numeric",
|
246
|
+
# "comparison" => "gt",
|
247
|
+
# "value" => 10 },
|
248
|
+
# "field" => "id"
|
249
|
+
# },
|
250
|
+
# "1" => {
|
251
|
+
# "data" => {
|
252
|
+
# "type" => "string",
|
253
|
+
# "value" => "pizza"
|
254
|
+
# },
|
255
|
+
# "field" => "food_name"
|
256
|
+
# }}
|
257
|
+
#
|
258
|
+
# =>
|
259
|
+
#
|
260
|
+
# {"id_gt" => 100, "food_name_contains" => "pizza"}
|
261
|
+
def convert_filters(column_filter)
|
262
|
+
res = {}
|
263
|
+
column_filter.each_pair do |k,v|
|
264
|
+
field = v["field"].dup
|
265
|
+
case v["data"]["type"]
|
266
|
+
when "string"
|
267
|
+
field << "_contains"
|
268
|
+
when "numeric"
|
269
|
+
field << "_#{v["data"]["comparison"]}"
|
270
|
+
end
|
271
|
+
value = v["data"]["value"]
|
272
|
+
res.merge!({field => value})
|
273
|
+
end
|
274
|
+
res
|
275
|
+
end
|
276
|
+
|
277
|
+
def normalize_extra_conditions(conditions)
|
278
|
+
conditions.convert_keys{|k| k.to_s.gsub("__", "_").to_sym}
|
279
|
+
end
|
280
|
+
|
281
|
+
# make params understandable to searchlogic
|
282
|
+
def normalize_params(params)
|
283
|
+
# filters
|
284
|
+
conditions = params[:filter] && convert_filters(params[:filter])
|
285
|
+
|
286
|
+
normalized_conditions = {}
|
287
|
+
conditions && conditions.each_pair do |k, v|
|
288
|
+
assoc, method = k.split('__')
|
289
|
+
normalized_conditions.merge!(method.nil? ? {assoc => v} : {assoc => {method => v}})
|
290
|
+
end
|
291
|
+
|
292
|
+
{:conditions => normalized_conditions}
|
293
|
+
end
|
294
|
+
|
295
|
+
## Edit in form specific API
|
296
|
+
def new_record_form__submit(params)
|
297
|
+
form_data = ActiveSupport::JSON.decode(params[:data])
|
298
|
+
|
299
|
+
# merge with strong default attirbutes
|
300
|
+
form_data.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
|
301
|
+
|
302
|
+
res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
|
303
|
+
|
304
|
+
if res[:set_form_values]
|
305
|
+
# successful creation
|
306
|
+
res[:set_form_values] = nil
|
307
|
+
res.merge!({
|
308
|
+
:parent => {:on_successfull_record_creation => true}
|
309
|
+
})
|
310
|
+
end
|
311
|
+
res.to_nifty_json
|
312
|
+
end
|
313
|
+
|
314
|
+
def check_for_positive_result(res)
|
315
|
+
if res[:set_form_values]
|
316
|
+
# successful creation
|
317
|
+
res[:set_form_values] = nil
|
318
|
+
res.merge!({
|
319
|
+
:parent => {:on_successfull_edit => true}
|
320
|
+
})
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def edit_form__submit(params)
|
325
|
+
form_data = ActiveSupport::JSON.decode(params[:data])
|
326
|
+
res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
|
327
|
+
|
328
|
+
check_for_positive_result(res)
|
329
|
+
|
330
|
+
res.to_nifty_json
|
331
|
+
end
|
332
|
+
|
333
|
+
def multi_edit_form__submit(params)
|
334
|
+
form_data = ActiveSupport::JSON.decode(params[:data])
|
335
|
+
form_instance = aggregatee_instance(:new_record_form)
|
336
|
+
|
337
|
+
ids = ActiveSupport::JSON.decode(params[:ids])
|
338
|
+
|
339
|
+
res = {}
|
340
|
+
ids.each do |id|
|
341
|
+
res = form_instance.create_or_update_record(form_data.merge("id" => id))
|
342
|
+
break if !res[:set_form_values]
|
343
|
+
end
|
344
|
+
|
345
|
+
check_for_positive_result(res)
|
346
|
+
|
347
|
+
res.to_nifty_json
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
end
|
352
|
+
end
|