netzke-basepack 0.5.12 → 0.5.13
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/CHANGELOG.rdoc +13 -0
- data/Rakefile +2 -2
- data/TODO.rdoc +4 -0
- data/lib/app/models/netzke_persistent_array_auto_model.rb +1 -1
- data/lib/netzke/active_record/attributes.rb +1 -1
- data/lib/netzke/active_record/combobox_options.rb +3 -33
- data/lib/netzke/data_accessor.rb +46 -26
- data/lib/netzke/form_panel.rb +11 -5
- data/lib/netzke/form_panel/form_panel_api.rb +5 -1
- data/lib/netzke/form_panel/form_panel_fields.rb +58 -56
- data/lib/netzke/grid_panel.rb +34 -38
- data/lib/netzke/grid_panel/grid_panel_api.rb +11 -2
- data/lib/netzke/grid_panel/grid_panel_columns.rb +79 -65
- data/lib/netzke/grid_panel/grid_panel_js.rb +2 -736
- data/lib/netzke/grid_panel/javascripts/advanced_search.js +65 -0
- data/lib/netzke/grid_panel/javascripts/edit_in_form.js +47 -0
- data/lib/netzke/grid_panel/javascripts/grid_panel_pre.js +562 -0
- data/lib/netzke/json_array_editor.rb +7 -1
- data/lib/netzke/tab_panel.rb +1 -0
- data/stylesheets/basepack.css +1 -1
- metadata +10 -7
@@ -79,13 +79,22 @@ module Netzke
|
|
79
79
|
end
|
80
80
|
|
81
81
|
# Returns choices for a column
|
82
|
-
def
|
82
|
+
def get_combobox_options_DELETME(params)
|
83
83
|
column = params[:column]
|
84
84
|
query = params[:query]
|
85
85
|
{:data => data_class.options_for(column, query).map{|s| [s]}}
|
86
86
|
# {:data => data_class.options_for(column, query).map{|s| [s]}}
|
87
87
|
end
|
88
88
|
|
89
|
+
# Returns choices for a column
|
90
|
+
def get_combobox_options(params)
|
91
|
+
column = columns.detect{ |c| c[:name] == params[:column] }.try(:to_options!)
|
92
|
+
scopes = (column[:editor].is_a?(Hash) && column[:editor] || {}).to_options[:scopes]
|
93
|
+
query = params[:query]
|
94
|
+
|
95
|
+
{:data => combobox_options_for_column(column, :query => query, :scopes => scopes)}
|
96
|
+
end
|
97
|
+
|
89
98
|
def move_rows(params)
|
90
99
|
if defined?(ActsAsList) && data_class.ancestors.include?(ActsAsList::InstanceMethods)
|
91
100
|
ids = JSON.parse(params[:ids]).reverse
|
@@ -175,7 +184,7 @@ module Netzke
|
|
175
184
|
normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
|
176
185
|
) if params[:extra_conditions]
|
177
186
|
|
178
|
-
search = data_class.
|
187
|
+
search = data_class.searchlogic(search_params)
|
179
188
|
|
180
189
|
# applying scopes
|
181
190
|
scopes.each do |s|
|
@@ -20,7 +20,7 @@ module Netzke
|
|
20
20
|
{:name => "default_value", :attr_type => :string, :width => 200},
|
21
21
|
|
22
22
|
# Options for drop-downs
|
23
|
-
{:name => "combobox_options", :attr_type => :string, :editor =>
|
23
|
+
{:name => "combobox_options", :attr_type => :string, :editor => :textarea, :width => 200},
|
24
24
|
|
25
25
|
# Whether the column is editable in the grid.
|
26
26
|
{:name => "read_only", :attr_type => :boolean, :header => "R/O", :tooltip => "Read-only"},
|
@@ -44,74 +44,65 @@ module Netzke
|
|
44
44
|
|
45
45
|
# Whether the column should be sortable (why change it? normally it's hardcoded)
|
46
46
|
{:name => "sortable", :attr_type => :boolean, :default_value => true, :hidden => true},
|
47
|
-
|
48
|
-
#
|
49
|
-
# And finally some meta columns that we probably never want to see in the GUI
|
50
|
-
#
|
51
|
-
{:name => "editor", :attr_type => :string, :meta => true}
|
52
47
|
]
|
53
48
|
end
|
54
49
|
|
55
50
|
end
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
initial_columns(only_included)
|
68
|
-
end
|
52
|
+
# Normalized columns for the grid, e.g.:
|
53
|
+
# [{:name => :id, :hidden => true, ...}, {:name => :name, :editable => false, ...}, ...]
|
54
|
+
def columns(only_included = true)
|
55
|
+
@columns ||= begin
|
56
|
+
if cols = load_columns
|
57
|
+
filter_out_excluded_columns(cols) if only_included
|
58
|
+
reverse_merge_equally_named_columns(cols, initial_columns)
|
59
|
+
cols
|
60
|
+
else
|
61
|
+
initial_columns(only_included)
|
69
62
|
end
|
70
63
|
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Columns that we fall back to when neither persistent columns, nor configured columns are present.
|
67
|
+
# If there's a model-level field configuration, it's being used.
|
68
|
+
# Otherwise the defaults straight from the ActiveRecord model ("netzke_attributes").
|
69
|
+
# Override this method if you want to provide a fix set of columns in your subclass.
|
70
|
+
def default_columns
|
71
|
+
@default_columns ||= load_model_level_attrs || data_class.netzke_attributes
|
72
|
+
end
|
73
|
+
|
74
|
+
# Columns that represent a smart merge of default_columns and columns passed during the configuration.
|
75
|
+
def initial_columns(only_included = true)
|
76
|
+
# Normalize here, as from the config we can get symbols (names) instead of hashes
|
77
|
+
columns_from_config = config[:columns] && normalize_attr_config(config[:columns])
|
71
78
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@default_columns ||= load_model_level_attrs || data_class.netzke_attributes
|
78
|
-
# @default_columns ||= load_model_level_attrs || data_class.netzke_attributes
|
79
|
-
end
|
80
|
-
|
81
|
-
# Columns that represent a smart merge of default_columns and columns passed during the configuration.
|
82
|
-
def initial_columns(only_included = true)
|
83
|
-
# Normalize here, as from the config we can get symbols (names) instead of hashes
|
84
|
-
columns_from_config = config[:columns] && normalize_attr_config(config[:columns])
|
85
|
-
|
86
|
-
if columns_from_config
|
87
|
-
# reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
|
88
|
-
for c in columns_from_config
|
89
|
-
corresponding_default_column = default_columns.find{ |k| k[:name] == c[:name] }
|
90
|
-
c.reverse_merge!(corresponding_default_column) if corresponding_default_column
|
91
|
-
end
|
92
|
-
columns_for_create = columns_from_config
|
93
|
-
else
|
94
|
-
# we didn't have columns configured in widget's config, so, use the columns from the data class
|
95
|
-
columns_for_create = default_columns
|
96
|
-
end
|
97
|
-
|
98
|
-
filter_out_excluded_columns(columns_for_create) if only_included
|
99
|
-
|
100
|
-
# Make the column config complete with the defaults
|
101
|
-
columns_for_create.each do |c|
|
102
|
-
detect_association(c)
|
103
|
-
set_default_header(c)
|
104
|
-
set_default_editor(c)
|
105
|
-
set_default_width(c)
|
106
|
-
set_default_hidden(c)
|
107
|
-
set_default_editable(c)
|
108
|
-
set_default_sortable(c)
|
109
|
-
set_default_filterable(c)
|
79
|
+
if columns_from_config
|
80
|
+
# reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
|
81
|
+
for c in columns_from_config
|
82
|
+
corresponding_default_column = default_columns.find{ |k| k[:name] == c[:name] }
|
83
|
+
c.reverse_merge!(corresponding_default_column) if corresponding_default_column
|
110
84
|
end
|
111
|
-
|
112
|
-
|
85
|
+
columns_for_create = columns_from_config
|
86
|
+
else
|
87
|
+
# we didn't have columns configured in widget's config, so, use the columns from the data class
|
88
|
+
columns_for_create = default_columns
|
113
89
|
end
|
114
90
|
|
91
|
+
filter_out_excluded_columns(columns_for_create) if only_included
|
92
|
+
|
93
|
+
# Make the column config complete with the defaults
|
94
|
+
columns_for_create.each do |c|
|
95
|
+
detect_association(c)
|
96
|
+
set_default_header(c)
|
97
|
+
set_default_editor(c)
|
98
|
+
set_default_width(c)
|
99
|
+
set_default_hidden(c)
|
100
|
+
set_default_editable(c)
|
101
|
+
set_default_sortable(c)
|
102
|
+
set_default_filterable(c)
|
103
|
+
end
|
104
|
+
|
105
|
+
columns_for_create
|
115
106
|
end
|
116
107
|
|
117
108
|
private
|
@@ -121,7 +112,7 @@ module Netzke
|
|
121
112
|
|
122
113
|
# Stores modified columns in persistent storage
|
123
114
|
def save_columns!
|
124
|
-
NetzkeFieldList.update_list_for_current_authority(global_id, columns(false),
|
115
|
+
NetzkeFieldList.update_list_for_current_authority(global_id, columns(false), original_data_class.name)
|
125
116
|
end
|
126
117
|
|
127
118
|
def load_columns
|
@@ -143,6 +134,7 @@ module Netzke
|
|
143
134
|
|
144
135
|
def set_default_editor(c)
|
145
136
|
c[:editor] ||= editor_for_attr_type(c[:attr_type])
|
137
|
+
c[:editor] = {:xtype => c[:editor]} if c[:editor].is_a?(Symbol)
|
146
138
|
end
|
147
139
|
|
148
140
|
def set_default_width(c)
|
@@ -160,11 +152,11 @@ module Netzke
|
|
160
152
|
end
|
161
153
|
|
162
154
|
def set_default_sortable(c)
|
163
|
-
c[:sortable] = !c[:virtual]
|
155
|
+
c[:sortable] = !c[:virtual] if c[:sortable].nil?
|
164
156
|
end
|
165
157
|
|
166
158
|
def set_default_filterable(c)
|
167
|
-
c[:filterable] = !c[:virtual]
|
159
|
+
c[:filterable] = !c[:virtual] if c[:filterable].nil?
|
168
160
|
end
|
169
161
|
|
170
162
|
# Returns editor's xtype for a column type
|
@@ -213,17 +205,39 @@ module Netzke
|
|
213
205
|
assoc_method_type = assoc_column.try(:type)
|
214
206
|
|
215
207
|
# if association column is boolean, display a checkbox (or alike), otherwise - a combobox (or alike)
|
216
|
-
c[:editor]
|
208
|
+
c[:editor] ||= assoc_method_type == :boolean ? editor_for_attr_type(:boolean) : editor_for_association
|
217
209
|
end
|
218
210
|
end
|
219
211
|
|
212
|
+
# Default fields that will be displayed in the Add/Edit/Search forms
|
220
213
|
def default_fields_for_forms
|
221
|
-
|
214
|
+
form_klass = "Netzke::ModelExtensions::#{config[:model]}ForFormPanel".constantize rescue nil
|
215
|
+
form_klass ||= original_data_class
|
216
|
+
|
217
|
+
# Select only those fields that are known to the form_klass
|
218
|
+
selected_columns = columns.select do |c|
|
219
|
+
form_klass.column_names.include?(c[:name]) ||
|
220
|
+
form_klass.instance_methods.include?("#{c[:name]}=") ||
|
221
|
+
association_attr?(c[:name])
|
222
|
+
end
|
223
|
+
|
224
|
+
selected_columns.map do |c|
|
225
|
+
field_config = {:name => c[:name]}
|
226
|
+
|
227
|
+
# scopes for combobox options
|
228
|
+
field_config[:scopes] = c[:editor].is_a?(Hash) && c[:editor][:scopes]
|
229
|
+
|
230
|
+
field_config
|
231
|
+
end
|
222
232
|
end
|
223
|
-
|
233
|
+
|
234
|
+
# Receives 2 arrays of columns. Merges the missing config from the +source+ into +dest+, matching columns by name
|
235
|
+
def reverse_merge_equally_named_columns(dest, source)
|
236
|
+
dest.each{ |dc| dc.reverse_merge!(source.detect{ |sc| sc[:name] == dc[:name] }) }
|
237
|
+
end
|
238
|
+
|
224
239
|
def self.included(receiver)
|
225
240
|
receiver.extend ClassMethods
|
226
|
-
receiver.send :include, InstanceMethods
|
227
241
|
end
|
228
242
|
end
|
229
243
|
end
|
@@ -18,7 +18,7 @@ module Netzke
|
|
18
18
|
module ClassMethods
|
19
19
|
|
20
20
|
def js_base_class
|
21
|
-
'
|
21
|
+
'Netzke.pre.GridPanel' # see grid_panel_pre.js
|
22
22
|
end
|
23
23
|
|
24
24
|
# Ext.Component#initComponent, built up from pices (dependent on class-level configuration)
|
@@ -45,233 +45,8 @@ module Netzke
|
|
45
45
|
# Result
|
46
46
|
<<-END_OF_JAVASCRIPT
|
47
47
|
function(){
|
48
|
-
if (!this.clmns) {this.feedback('No columns defined for grid '+this.id);}
|
49
|
-
|
50
|
-
/* Process columns - all in sake of creating the column model */
|
51
|
-
// Normalize columns passed in the config
|
52
|
-
var normClmns = [];
|
53
|
-
Ext.each(this.clmns, function(c){
|
54
|
-
// normalize columns
|
55
|
-
if (typeof c == 'string') {
|
56
|
-
normClmns.push({name:c});
|
57
|
-
} else {
|
58
|
-
normClmns.push(c);
|
59
|
-
}
|
60
|
-
});
|
61
|
-
|
62
|
-
delete this.clmns; // we don't need them anymore
|
63
|
-
|
64
|
-
var cmConfig = []; // column model config - we'll use it later to create the ColumnModel
|
65
|
-
this.plugins = []; // checkbox colums is a special case, being a plugin
|
66
|
-
|
67
|
-
var filters = [];
|
68
|
-
|
69
|
-
// Run through columns and set up different configuration for each
|
70
|
-
Ext.each(normClmns, function(c){
|
71
|
-
// We will not use meta columns as actual columns (not even hidden) - only to create the records
|
72
|
-
if (c.meta) return;
|
73
|
-
|
74
|
-
// Apply default column config
|
75
|
-
Ext.applyIf(c, this.defaultColumnConfig);
|
76
|
-
|
77
|
-
// setting dataIndex separately
|
78
|
-
c.dataIndex = c.name;
|
79
|
-
|
80
|
-
// Automatically calculated default values
|
81
|
-
if (!c.header) {c.header = c.label || c.name.humanize()}
|
82
|
-
|
83
|
-
// normalize editor
|
84
|
-
if (c.editor) {
|
85
|
-
c.editor = Netzke.isObject(c.editor) ? c.editor : {xtype:c.editor};
|
86
|
-
} else {
|
87
|
-
c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
|
88
|
-
}
|
89
|
-
|
90
|
-
// if comboboxOptions are provided, we render a combobox instead of textfield
|
91
|
-
if (c.comboboxOptions && c.editor.xtype === "textfield") {
|
92
|
-
c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
|
93
|
-
}
|
94
|
-
|
95
|
-
// collect filters
|
96
|
-
if (c.filterable){
|
97
|
-
filters.push({type:this.filterTypeForAttrType(c.attrType), dataIndex:c.name});
|
98
|
-
}
|
99
|
-
|
100
|
-
if (c.editor && c.editor.xtype == 'checkbox') {
|
101
|
-
// Special case of checkbox column
|
102
|
-
var plugin = new Ext.ux.grid.CheckColumn(c);
|
103
|
-
this.plugins.push(plugin);
|
104
|
-
cmConfig.push(plugin);
|
105
|
-
} else {
|
106
|
-
// a "normal" column, not a plugin
|
107
|
-
if (!c.readOnly && !this.prohibitUpdate) {
|
108
|
-
// c.editor contains complete config of the editor
|
109
|
-
var xtype = c.editor.xtype;
|
110
|
-
c.editor = Ext.ComponentMgr.create(Ext.apply({
|
111
|
-
parentId: this.id,
|
112
|
-
name: c.name,
|
113
|
-
selectOnFocus:true
|
114
|
-
}, c.editor));
|
115
|
-
} else {
|
116
|
-
c.editor = null;
|
117
|
-
}
|
118
|
-
|
119
|
-
// Normalize the renderer
|
120
|
-
this.normalizeRenderer(c);
|
121
|
-
|
122
|
-
// set the renderer
|
123
|
-
// if (c.renderer && !Ext.isArray(c.renderer) && c.renderer.match(/^\\s*function\\s*\\(/)) {
|
124
|
-
// // if the renderer is an inline function - eval it (double escaping because we are inside of the Ruby string here...)
|
125
|
-
// eval("c.renderer = " + c.renderer + ";");
|
126
|
-
// } else if (Ext.isFunction(this[c.renderer])) {
|
127
|
-
// // whether the renderer is defined in this.scope
|
128
|
-
// c.renderer = this[c.renderer].createDelegate(this);
|
129
|
-
// } else {
|
130
|
-
// // othrewise it's a string representing the name of the renderer or a json-encoded array,
|
131
|
-
// // where the first parameter is the renderer's name, and the rest - parameters that should be
|
132
|
-
// // passed to the renderer at the moment of calling
|
133
|
-
// var renderer = Ext.netzke.normalizedRenderer(c.renderer);
|
134
|
-
// if (renderer != null) {
|
135
|
-
// c.renderer = renderer
|
136
|
-
// };
|
137
|
-
// }
|
138
|
-
//
|
139
|
-
// add to the list
|
140
|
-
cmConfig.push(c);
|
141
|
-
}
|
142
|
-
|
143
|
-
}, this);
|
144
|
-
|
145
|
-
// Finally, create the ColumnModel based on processed columns
|
146
|
-
this.cm = new Ext.grid.ColumnModel(cmConfig);
|
147
|
-
|
148
|
-
// Hidden change event
|
149
|
-
if (this.persistentConfig) {this.cm.on('hiddenchange', this.onColumnHiddenChange, this);}
|
150
|
-
|
151
|
-
/* ... and done with columns */
|
152
|
-
|
153
|
-
// Filters
|
154
|
-
if (this.enableColumnFilters) {
|
155
|
-
this.plugins.push(new Ext.ux.grid.GridFilters({filters:filters}));
|
156
|
-
}
|
157
|
-
|
158
|
-
// Create Ext.data.Record constructor specific for our particular column configuration
|
159
|
-
this.recordConfig = [];
|
160
|
-
Ext.each(normClmns, function(column){this.recordConfig.push({name:column.name, defaultValue:column.defaultValue});}, this);
|
161
|
-
this.Row = Ext.data.Record.create(this.recordConfig);
|
162
|
-
|
163
|
-
// Drag'n'Drop
|
164
|
-
if (this.enableRowsReordering){
|
165
|
-
this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
|
166
|
-
scrollable: true // enable scrolling support (default is false)
|
167
|
-
});
|
168
|
-
this.plugins.push(this.ddPlugin);
|
169
|
-
}
|
170
|
-
|
171
|
-
// Explicitely create the connection to get grid's data,
|
172
|
-
// because we don't want the app-wide Ext.Ajax to be used,
|
173
|
-
// as we are going to subscribe to its events
|
174
|
-
var connection = new Ext.data.Connection({
|
175
|
-
url: this.buildApiUrl("get_data"),
|
176
|
-
extraParams: {
|
177
|
-
authenticity_token : Netzke.authenticityToken
|
178
|
-
},
|
179
|
-
|
180
|
-
// inform Ext.Ajax about our events
|
181
|
-
listeners: {
|
182
|
-
beforerequest: function(){
|
183
|
-
Ext.Ajax.fireEvent('beforerequest', arguments);
|
184
|
-
},
|
185
|
-
requestexception: function(){
|
186
|
-
Ext.Ajax.fireEvent('requestexception', arguments);
|
187
|
-
},
|
188
|
-
requestcomplete: function(){
|
189
|
-
Ext.Ajax.fireEvent('requestcomplete', arguments);
|
190
|
-
}
|
191
|
-
}
|
192
|
-
});
|
193
|
-
|
194
|
-
// besides getting data into the store, we may also get commands to execute
|
195
|
-
connection.on('requestcomplete', function(conn, r){
|
196
|
-
var response = Ext.decode(r.responseText);
|
197
|
-
|
198
|
-
// delete data-related properties
|
199
|
-
Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
|
200
|
-
this.bulkExecute(response);
|
201
|
-
}, this);
|
202
|
-
|
203
|
-
// HttpProxy that uses our custom connection
|
204
|
-
var httpProxy = new Ext.data.HttpProxy(connection);
|
205
|
-
|
206
|
-
// Data store
|
207
|
-
this.store = new Ext.data.Store({
|
208
|
-
proxy: this.proxy = httpProxy,
|
209
|
-
reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
|
210
|
-
remoteSort: true,
|
211
|
-
listeners:{'loadexception':{
|
212
|
-
fn:this.loadExceptionHandler,
|
213
|
-
scope:this
|
214
|
-
}}
|
215
|
-
});
|
216
|
-
|
217
|
-
// Normalize bottom bar
|
218
|
-
this.bbar = (this.enablePagination) ? new Ext.PagingToolbar({
|
219
|
-
pageSize : this.rowsPerPage,
|
220
|
-
items : this.bbar ? ["-"].concat(this.bbar) : [],
|
221
|
-
store : this.store,
|
222
|
-
emptyMsg: 'Empty',
|
223
|
-
displayInfo: true
|
224
|
-
}) : this.bbar;
|
225
|
-
|
226
|
-
// Selection model
|
227
|
-
this.sm = new Ext.grid.RowSelectionModel();
|
228
|
-
|
229
|
-
// Now let Ext.grid.EditorGridPanel do the rest
|
230
48
|
// Original initComponent
|
231
49
|
#{js_full_class_name}.superclass.initComponent.call(this);
|
232
|
-
|
233
|
-
// Inform the server part about column operations
|
234
|
-
if (this.persistentConfig) {
|
235
|
-
this.on('columnresize', this.onColumnResize, this);
|
236
|
-
this.on('columnmove', this.onColumnMove, this);
|
237
|
-
}
|
238
|
-
|
239
|
-
// Context menu
|
240
|
-
if (this.enableContextMenu) {
|
241
|
-
this.on('rowcontextmenu', this.onRowContextMenu, this);
|
242
|
-
}
|
243
|
-
|
244
|
-
// Load data AFTER the toolbar is bound to the store, which will provide for correct page number
|
245
|
-
if (this.loadInlineData) {
|
246
|
-
this.getStore().loadData(this.inlineData);
|
247
|
-
|
248
|
-
// If rows per page specified, fake store.lastOptions as if the data was loaded
|
249
|
-
// by PagingToolbar (for correct functionning of refresh tool and extended search)
|
250
|
-
if (this.rowsPerPage) {
|
251
|
-
this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
|
252
|
-
}
|
253
|
-
|
254
|
-
// inlineData may also contain commands (TODO: make it DRY)
|
255
|
-
// delete data-related properties
|
256
|
-
Ext.each(['data', 'total', 'success'], function(property){delete this.inlineData[property];}, this);
|
257
|
-
this.bulkExecute(this.inlineData);
|
258
|
-
}
|
259
|
-
|
260
|
-
// Process selectionchange event
|
261
|
-
this.getSelectionModel().on('selectionchange', function(selModel){
|
262
|
-
// enable/disable actions
|
263
|
-
this.actions.del.setDisabled(!selModel.hasSelection() || this.prohibitDelete);
|
264
|
-
this.actions.edit.setDisabled(selModel.getCount() != 1 || this.prohibitUpdate);
|
265
|
-
}, this);
|
266
|
-
|
267
|
-
// Drag n Drop event
|
268
|
-
if (this.enableRowsReordering){
|
269
|
-
this.ddPlugin.on('afterrowmove', this.onAfterRowMove, this);
|
270
|
-
}
|
271
|
-
|
272
|
-
// GridView
|
273
|
-
this.getView().getRowClass = this.defaultGetRowClass;
|
274
|
-
|
275
50
|
#{edit_in_form_events}
|
276
51
|
}
|
277
52
|
|
@@ -281,518 +56,9 @@ module Netzke
|
|
281
56
|
|
282
57
|
# All the rest that makes our JavaScript class
|
283
58
|
def js_extend_properties
|
284
|
-
res = super
|
285
|
-
|
286
|
-
res.merge!(
|
287
|
-
{
|
288
|
-
# all the options are overrideable in config, of course
|
289
|
-
:track_mouse_over => true,
|
290
|
-
:load_mask => true,
|
291
|
-
:auto_scroll => true,
|
292
|
-
|
293
|
-
:default_column_config => meta_columns.inject({}){ |r, c| c.is_a?(Hash) ? r.merge(c[:name] => c[:default]) : r },
|
294
|
-
|
295
|
-
:init_component => js_init_component.l,
|
296
|
-
|
297
|
-
:attr_type_editor_map => {
|
298
|
-
:integer => "numberfield",
|
299
|
-
:boolean => "checkbox",
|
300
|
-
:decimal => "numberfield",
|
301
|
-
:datetime => "xdatetime",
|
302
|
-
:date => "datefield",
|
303
|
-
:string => "textfield"
|
304
|
-
},
|
305
|
-
|
306
|
-
:filter_type_for_attr_type => <<-END_OF_JAVASCRIPT.l,
|
307
|
-
function(attrType){
|
308
|
-
var map = {
|
309
|
-
integer :'Numeric',
|
310
|
-
decimal :'Numeric',
|
311
|
-
datetime:'Date',
|
312
|
-
date :'Date',
|
313
|
-
string :'String'
|
314
|
-
};
|
315
|
-
map['boolean'] = "Boolean"; // "boolean" is a JS reserved word
|
316
|
-
return map[attrType] || 'String';
|
317
|
-
}
|
318
|
-
END_OF_JAVASCRIPT
|
319
|
-
|
320
|
-
# Handlers for actions
|
321
|
-
#
|
322
|
-
|
323
|
-
:on_add => <<-END_OF_JAVASCRIPT.l,
|
324
|
-
function(){
|
325
|
-
var r = new this.Row();
|
326
|
-
r.isNew = true; // to distinguish new records
|
327
|
-
// r.set('id', r.id); // otherwise later r.get('id') returns empty string
|
328
|
-
this.stopEditing();
|
329
|
-
this.getStore().add(r);
|
330
|
-
|
331
|
-
// Set default values
|
332
|
-
this.getStore().fields.each(function(field){
|
333
|
-
r.set(field.name, field.defaultValue);
|
334
|
-
});
|
335
|
-
|
336
|
-
this.tryStartEditing(this.store.indexOf(r));
|
337
|
-
}
|
338
|
-
END_OF_JAVASCRIPT
|
339
|
-
|
340
|
-
:on_edit => <<-END_OF_JAVASCRIPT.l,
|
341
|
-
function(){
|
342
|
-
var row = this.getSelectionModel().getSelected();
|
343
|
-
if (row){
|
344
|
-
this.tryStartEditing(this.store.indexOf(row));
|
345
|
-
}
|
346
|
-
}
|
347
|
-
END_OF_JAVASCRIPT
|
348
|
-
|
349
|
-
:on_del => <<-END_OF_JAVASCRIPT.l,
|
350
|
-
function() {
|
351
|
-
Ext.Msg.confirm('Confirm', 'Are you sure?', function(btn){
|
352
|
-
if (btn == 'yes') {
|
353
|
-
var records = [];
|
354
|
-
this.getSelectionModel().each(function(r){
|
355
|
-
if (r.isNew) {
|
356
|
-
// this record is not know to server - simply remove from store
|
357
|
-
this.store.remove(r);
|
358
|
-
} else {
|
359
|
-
records.push(r.id);
|
360
|
-
}
|
361
|
-
}, this);
|
362
|
-
|
363
|
-
if (records.length > 0){
|
364
|
-
// call API
|
365
|
-
this.deleteData({records: Ext.encode(records)});
|
366
|
-
}
|
367
|
-
}
|
368
|
-
}, this);
|
369
|
-
}
|
370
|
-
END_OF_JAVASCRIPT
|
371
|
-
|
372
|
-
:on_apply => <<-END_OF_JAVASCRIPT.l,
|
373
|
-
function(){
|
374
|
-
var newRecords = [];
|
375
|
-
var updatedRecords = [];
|
376
|
-
Ext.each(this.store.getModifiedRecords(),
|
377
|
-
function(r) {
|
378
|
-
if (r.isNew) {
|
379
|
-
newRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
|
380
|
-
} else {
|
381
|
-
updatedRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
|
382
|
-
}
|
383
|
-
},
|
384
|
-
this);
|
385
|
-
|
386
|
-
if (newRecords.length > 0 || updatedRecords.length > 0) {
|
387
|
-
var params = {};
|
388
|
-
|
389
|
-
if (newRecords.length > 0) {
|
390
|
-
params.created_records = Ext.encode(newRecords);
|
391
|
-
}
|
392
|
-
|
393
|
-
if (updatedRecords.length > 0) {
|
394
|
-
params.updated_records = Ext.encode(updatedRecords);
|
395
|
-
}
|
396
|
-
|
397
|
-
if (this.store.baseParams !== {}) {
|
398
|
-
params.base_params = Ext.encode(this.store.baseParams);
|
399
|
-
}
|
400
|
-
|
401
|
-
this.postData(params);
|
402
|
-
}
|
403
|
-
|
404
|
-
}
|
405
|
-
END_OF_JAVASCRIPT
|
406
|
-
|
407
|
-
# Handlers for tools
|
408
|
-
#
|
409
|
-
|
410
|
-
:on_refresh => <<-END_OF_JAVASCRIPT.l,
|
411
|
-
function() {
|
412
|
-
if (this.fireEvent('refresh', this) !== false) {
|
413
|
-
this.store.reload();
|
414
|
-
}
|
415
|
-
}
|
416
|
-
END_OF_JAVASCRIPT
|
417
|
-
|
418
|
-
# Event handlers
|
419
|
-
#
|
420
|
-
|
421
|
-
:on_column_resize => <<-END_OF_JAVASCRIPT.l,
|
422
|
-
function(index, size){
|
423
|
-
this.resizeColumn({
|
424
|
-
index:index,
|
425
|
-
size:size
|
426
|
-
});
|
427
|
-
}
|
428
|
-
END_OF_JAVASCRIPT
|
429
|
-
|
430
|
-
:on_column_hidden_change => <<-END_OF_JAVASCRIPT.l,
|
431
|
-
function(cm, index, hidden){
|
432
|
-
this.hideColumn({
|
433
|
-
index:index,
|
434
|
-
hidden:hidden
|
435
|
-
});
|
436
|
-
}
|
437
|
-
END_OF_JAVASCRIPT
|
438
|
-
|
439
|
-
:on_column_move => <<-END_OF_JAVASCRIPT.l,
|
440
|
-
function(oldIndex, newIndex){
|
441
|
-
this.moveColumn({
|
442
|
-
old_index:oldIndex,
|
443
|
-
new_index:newIndex
|
444
|
-
});
|
445
|
-
|
446
|
-
var newRecordConfig = [];
|
447
|
-
Ext.each(this.getColumnModel().config, function(c){newRecordConfig.push({name: c.name})});
|
448
|
-
delete this.Row; // old record constructor
|
449
|
-
this.Row = Ext.data.Record.create(newRecordConfig);
|
450
|
-
this.getStore().reader.recordType = this.Row;
|
451
|
-
}
|
452
|
-
END_OF_JAVASCRIPT
|
453
|
-
|
454
|
-
:on_row_context_menu => <<-END_OF_JAVASCRIPT.l,
|
455
|
-
function(grid, rowIndex, e){
|
456
|
-
e.stopEvent();
|
457
|
-
var coords = e.getXY();
|
458
|
-
|
459
|
-
if (!grid.getSelectionModel().isSelected(rowIndex)) {
|
460
|
-
grid.getSelectionModel().selectRow(rowIndex);
|
461
|
-
}
|
462
|
-
|
463
|
-
var menu = new Ext.menu.Menu({
|
464
|
-
items: this.contextMenu
|
465
|
-
});
|
466
|
-
|
467
|
-
menu.showAt(coords);
|
468
|
-
}
|
469
|
-
END_OF_JAVASCRIPT
|
470
|
-
|
471
|
-
:on_after_row_move => <<-END_OF_JAVASCRIPT.l,
|
472
|
-
function(dt, oldIndex, newIndex, records){
|
473
|
-
var ids = [];
|
474
|
-
// collect records ids
|
475
|
-
Ext.each(records, function(r){ids.push(r.id)});
|
476
|
-
// call GridPanel's API
|
477
|
-
this.moveRows({ids:Ext.encode(ids), new_index: newIndex});
|
478
|
-
}
|
479
|
-
END_OF_JAVASCRIPT
|
480
|
-
|
481
|
-
# Other methods
|
482
|
-
#
|
483
|
-
|
484
|
-
:load_exception_handler => <<-END_OF_JAVASCRIPT.l,
|
485
|
-
function(proxy, options, response, error){
|
486
|
-
if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
|
487
|
-
this.feedback(responseObject.flash);
|
488
|
-
} else {
|
489
|
-
if (error){
|
490
|
-
this.feedback(error.message);
|
491
|
-
} else {
|
492
|
-
this.feedback(response.statusText);
|
493
|
-
}
|
494
|
-
}
|
495
|
-
}
|
496
|
-
END_OF_JAVASCRIPT
|
497
|
-
|
498
|
-
:update => <<-END_OF_JAVASCRIPT.l,
|
499
|
-
function(){
|
500
|
-
this.store.reload();
|
501
|
-
}
|
502
|
-
END_OF_JAVASCRIPT
|
503
|
-
|
504
|
-
:load_store_data => <<-END_OF_JAVASCRIPT.l,
|
505
|
-
function(data){
|
506
|
-
this.store.loadData(data);
|
507
|
-
Ext.each(['data', 'total', 'success'], function(property){delete data[property];}, this);
|
508
|
-
this.bulkExecute(data);
|
509
|
-
}
|
510
|
-
END_OF_JAVASCRIPT
|
511
|
-
|
512
|
-
# try editing the first editable (i.e. not hidden, not read-only) sell
|
513
|
-
:try_start_editing => <<-END_OF_JAVASCRIPT.l,
|
514
|
-
function(row){
|
515
|
-
var editableIndex = 0;
|
516
|
-
Ext.each(this.getColumnModel().config, function(c){
|
517
|
-
// skip columns that cannot be edited
|
518
|
-
if (!(c.hidden == true || c.editable == false || !c.editor || c.attrType == 'boolean')) {
|
519
|
-
return false;
|
520
|
-
}
|
521
|
-
editableIndex++;
|
522
|
-
});
|
523
|
-
|
524
|
-
if (editableIndex < this.getColumnModel().config.length) {this.startEditing(row, editableIndex);}
|
525
|
-
}
|
526
|
-
END_OF_JAVASCRIPT
|
527
|
-
|
528
|
-
# Called by the server side to update newly created records
|
529
|
-
:update_new_records => <<-END_OF_JAVASCRIPT.l,
|
530
|
-
function(records){
|
531
|
-
this.updateRecords(records);
|
532
|
-
}
|
533
|
-
END_OF_JAVASCRIPT
|
534
|
-
|
535
|
-
# Called by the server side to update modified records
|
536
|
-
:update_mod_records => <<-END_OF_JAVASCRIPT.l,
|
537
|
-
function(records){
|
538
|
-
this.updateRecords(records, true);
|
539
|
-
}
|
540
|
-
END_OF_JAVASCRIPT
|
541
|
-
|
542
|
-
# Updates modified or newly created records, by record ID
|
543
|
-
# Example of the records argument (updated columns):
|
544
|
-
# {1098 => [1, 'value1', 'value2'], 1099 => [2, 'value1', 'value2']}
|
545
|
-
# Example of the records argument (new columns, id autogenerated by Ext):
|
546
|
-
# {"ext-record-200" => [1, 'value1', 'value2']}
|
547
|
-
:update_records => <<-END_OF_JAVASCRIPT.l,
|
548
|
-
function(records, mod){
|
549
|
-
if (!mod) {mod = false;}
|
550
|
-
var modRecordsInGrid = [].concat(this.store.getModifiedRecords()); // there must be a better way to clone an array...
|
551
|
-
|
552
|
-
// replace arrays of data in the args object with Ext.data.Record objects
|
553
|
-
for (var k in records){
|
554
|
-
records[k] = this.store.reader.readRecords([records[k]]).records[0];
|
555
|
-
}
|
556
|
-
|
557
|
-
// for each new record write the data returned by the server, and commit the record
|
558
|
-
Ext.each(modRecordsInGrid, function(recordInGrid){
|
559
|
-
if (mod ^ recordInGrid.isNew) {
|
560
|
-
// if record is new, we access its id by "id", otherwise, the id is in the primary key column
|
561
|
-
var recordId = recordInGrid.id;
|
562
|
-
// new data that the server sent us to update this record (identified by the id)
|
563
|
-
var newData = records[recordId];
|
564
|
-
|
565
|
-
if (newData){
|
566
|
-
for (var k in newData.data){
|
567
|
-
recordInGrid.set(k, newData.get(k));
|
568
|
-
}
|
569
|
-
|
570
|
-
recordInGrid.isNew = false;
|
571
|
-
recordInGrid.commit();
|
572
|
-
}
|
573
|
-
|
574
|
-
}
|
575
|
-
}, this);
|
576
|
-
|
577
|
-
// clear the selections
|
578
|
-
this.getSelectionModel().clearSelections();
|
579
|
-
|
580
|
-
// check if there are still records with errors
|
581
|
-
var modRecords = this.store.getModifiedRecords();
|
582
|
-
if (modRecords.length == 0) {
|
583
|
-
// if all records are accepted, reload the grid (so that eventual order/filtering is correct)
|
584
|
-
this.store.reload();
|
585
|
-
|
586
|
-
// ... and set default getRowClass function
|
587
|
-
this.getView().getRowClass = this.defaultGetRowClass;
|
588
|
-
} else {
|
589
|
-
this.getView().getRowClass = function(r){
|
590
|
-
return r.dirty ? "grid-dirty-record" : ""
|
591
|
-
}
|
592
|
-
}
|
593
|
-
|
594
|
-
this.getView().refresh();
|
595
|
-
this.getSelectionModel().fireEvent('selectionchange', this.getSelectionModel());
|
596
|
-
}
|
597
|
-
END_OF_JAVASCRIPT
|
598
|
-
|
599
|
-
:default_get_row_class => <<-END_OF_JAVASCRIPT.l,
|
600
|
-
function(r){
|
601
|
-
return r.isNew ? "grid-dirty-record" : ""
|
602
|
-
}
|
603
|
-
END_OF_JAVASCRIPT
|
604
|
-
|
605
|
-
:select_first_row => <<-END_OF_JAVASCRIPT.l,
|
606
|
-
function(){
|
607
|
-
this.getSelectionModel().suspendEvents();
|
608
|
-
this.getSelectionModel().selectRow(0);
|
609
|
-
this.getSelectionModel().resumeEvents();
|
610
|
-
}
|
611
|
-
END_OF_JAVASCRIPT
|
612
|
-
|
613
|
-
# Normalizes the renderer for a column.
|
614
|
-
# Renderer may be:
|
615
|
-
# 1) a string that contains the name of the function to be used as renderer.
|
616
|
-
# 2) an array, where the first element is the function name, and the rest - the arguments
|
617
|
-
# that will be passed to that function along with the value to be rendered.
|
618
|
-
# The function is searched in the following objects: 1) Ext.util.Format, 2) this.
|
619
|
-
# If not found, it is simply evaluated. Handy, when as renderer we receive an inline JS function,
|
620
|
-
# or reference to a function in some other scope.
|
621
|
-
# So, these will work:
|
622
|
-
# * "uppercase"
|
623
|
-
# * ["ellipsis", 10]
|
624
|
-
# * ["substr", 3, 5]
|
625
|
-
# * "myRenderer" (if this.myRenderer is a function)
|
626
|
-
# * ["Some.scope.Format.customRenderer", 10, 20, 30] (if Some.scope.Format.customRenderer is a function)
|
627
|
-
# * "function(v){ return 'Value: ' + v; }"
|
628
|
-
:normalize_renderer => <<-END_OF_JAVASCRIPT.l,
|
629
|
-
function(c) {
|
630
|
-
if (!c.renderer) return;
|
631
|
-
|
632
|
-
var name, args = [];
|
633
|
-
|
634
|
-
if ('string' === typeof c.renderer) {
|
635
|
-
name = c.renderer;
|
636
|
-
} else {
|
637
|
-
name = c.renderer[0];
|
638
|
-
args = c.renderer.slice(1);
|
639
|
-
}
|
640
|
-
|
641
|
-
// First check whether Ext.util.Format has it
|
642
|
-
if (Ext.isFunction(Ext.util.Format[name])) {
|
643
|
-
c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
|
644
|
-
} else if (Ext.isFunction(this[name])) {
|
645
|
-
// ... then if our own class has it
|
646
|
-
c.renderer = this[name].createDelegate(this, args, 1);
|
647
|
-
} else {
|
648
|
-
// ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
|
649
|
-
eval("c.renderer = " + c.renderer + ";");
|
650
|
-
}
|
651
|
-
}
|
652
|
-
END_OF_JAVASCRIPT
|
653
|
-
|
654
|
-
|
655
|
-
# :reorder_columns => <<-END_OF_JAVASCRIPT.l,
|
656
|
-
# function(columns){
|
657
|
-
# columnsInNewShipment = [];
|
658
|
-
# Ext.each(columns, function(c){
|
659
|
-
# columnsInNewShipment.push({name:c});
|
660
|
-
# });
|
661
|
-
# newRecordType = Ext.data.Record.create(columnsInNewShipment);
|
662
|
-
# this.store.reader.recordType = newRecordType; // yes, recordType is a protected property, but that's the only way we can do it, and it seems to work for now
|
663
|
-
# }
|
664
|
-
# END_OF_JAVASCRIPT
|
665
|
-
}
|
666
|
-
)
|
667
|
-
|
668
|
-
# Optional edit in form functionality
|
669
|
-
res.merge!(
|
670
59
|
{
|
671
|
-
:
|
672
|
-
function(){
|
673
|
-
var selModel = this.getSelectionModel();
|
674
|
-
if (selModel.getCount() > 1) {
|
675
|
-
var recordId = selModel.getSelected().id;
|
676
|
-
this.loadAggregatee({id: "multiEditForm",
|
677
|
-
params: {record_id: recordId},
|
678
|
-
callback: function(w){
|
679
|
-
var form = w.items.first();
|
680
|
-
form.on('apply', function(){
|
681
|
-
var ids = [];
|
682
|
-
selModel.each(function(r){
|
683
|
-
ids.push(r.id);
|
684
|
-
});
|
685
|
-
form.baseParams = {ids: Ext.encode(ids)}
|
686
|
-
}, this);
|
687
|
-
|
688
|
-
w.on('close', function(){
|
689
|
-
if (w.closeRes === "ok") {
|
690
|
-
this.store.reload();
|
691
|
-
}
|
692
|
-
}, this);
|
693
|
-
}, scope: this});
|
694
|
-
} else {
|
695
|
-
var recordId = selModel.getSelected().id;
|
696
|
-
this.loadAggregatee({id: "editForm",
|
697
|
-
params: {record_id: recordId},
|
698
|
-
callback: function(form){
|
699
|
-
form.on('close', function(){
|
700
|
-
if (form.closeRes === "ok") {
|
701
|
-
this.store.reload();
|
702
|
-
}
|
703
|
-
}, this);
|
704
|
-
}, scope: this});
|
705
|
-
}
|
706
|
-
}
|
707
|
-
END_OF_JAVASCRIPT
|
708
|
-
|
709
|
-
:on_add_in_form => <<-END_OF_JAVASCRIPT.l,
|
710
|
-
function(){
|
711
|
-
this.loadAggregatee({id: "addForm", callback: function(form){
|
712
|
-
form.on('close', function(){
|
713
|
-
if (form.closeRes === "ok") {
|
714
|
-
this.store.reload();
|
715
|
-
}
|
716
|
-
}, this);
|
717
|
-
}, scope: this});
|
718
|
-
}
|
719
|
-
END_OF_JAVASCRIPT
|
60
|
+
:init_component => js_init_component.l
|
720
61
|
}
|
721
|
-
) if config[:edit_in_form_available]
|
722
|
-
|
723
|
-
# Optional extended search functionality
|
724
|
-
res.merge!(
|
725
|
-
{
|
726
|
-
:on_search => <<-END_OF_JAVASCRIPT.l,
|
727
|
-
function(){
|
728
|
-
delete this.searchWindow;
|
729
|
-
this.searchWindow = new Ext.Window({
|
730
|
-
title:'Advanced search',
|
731
|
-
layout:'fit',
|
732
|
-
modal: true,
|
733
|
-
width: 400,
|
734
|
-
height: Ext.lib.Dom.getViewHeight() *0.9,
|
735
|
-
closeAction:'close',
|
736
|
-
buttons:[{
|
737
|
-
text: 'OK',
|
738
|
-
handler: function(){
|
739
|
-
this.ownerCt.ownerCt.closePositively();
|
740
|
-
}
|
741
|
-
},{
|
742
|
-
text:'Cancel',
|
743
|
-
handler:function(){
|
744
|
-
this.ownerCt.ownerCt.closeNegatively();
|
745
|
-
}
|
746
|
-
}],
|
747
|
-
closePositively : function(){
|
748
|
-
this.conditions = this.getWidget().getForm().getValues();
|
749
|
-
this.closeRes = 'OK';
|
750
|
-
this.close();
|
751
|
-
},
|
752
|
-
closeNegatively: function(){
|
753
|
-
this.closeRes = 'cancel';
|
754
|
-
this.close();
|
755
|
-
}
|
756
|
-
});
|
757
|
-
|
758
|
-
this.searchWindow.on('close', function(){
|
759
|
-
if (this.searchWindow.closeRes == 'OK'){
|
760
|
-
var searchConditions = this.searchWindow.conditions;
|
761
|
-
var filtered = false;
|
762
|
-
// check if there's any search condition set
|
763
|
-
for (var k in searchConditions) {
|
764
|
-
if (searchConditions[k].length > 0) {
|
765
|
-
filtered = true;
|
766
|
-
break;
|
767
|
-
}
|
768
|
-
}
|
769
|
-
this.actions.search.setText(filtered ? "Search *" : "Search");
|
770
|
-
this.getStore().baseParams = {extra_conditions: Ext.encode(this.searchWindow.conditions)};
|
771
|
-
this.getStore().load();
|
772
|
-
}
|
773
|
-
}, this);
|
774
|
-
|
775
|
-
this.searchWindow.on('add', function(container, searchPanel){
|
776
|
-
searchPanel.on('apply', function(widget){
|
777
|
-
this.searchWindow.closePositively();
|
778
|
-
return false; // stop the event
|
779
|
-
}, this);
|
780
|
-
}, this);
|
781
|
-
|
782
|
-
this.searchWindow.show(null, function(){
|
783
|
-
this.searchWindow.closeRes = 'cancel';
|
784
|
-
if (!this.searchWindow.getWidget()){
|
785
|
-
this.loadAggregatee({id:"searchPanel", container:this.searchWindow.id});
|
786
|
-
}
|
787
|
-
}, this);
|
788
|
-
|
789
|
-
}
|
790
|
-
END_OF_JAVASCRIPT
|
791
|
-
|
792
|
-
}
|
793
|
-
) if config[:extended_search_available]
|
794
|
-
|
795
|
-
res
|
796
62
|
end
|
797
63
|
end
|
798
64
|
|