netzke-basepack 0.6.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/CHANGELOG.rdoc +7 -4
  2. data/README.md +63 -0
  3. data/Rakefile +4 -4
  4. data/javascripts/basepack.js +113 -133
  5. data/lib/netzke-basepack.rb +9 -2
  6. data/lib/netzke/basepack.rb +9 -6
  7. data/lib/netzke/basepack/accordion_panel.rb +1 -1
  8. data/lib/netzke/basepack/auth_app.rb +28 -21
  9. data/lib/netzke/basepack/border_layout_panel.rb +9 -57
  10. data/lib/netzke/basepack/border_layout_panel/javascripts/border_layout_panel.js +40 -0
  11. data/lib/netzke/basepack/data_accessor.rb +3 -5
  12. data/lib/netzke/basepack/form_panel.rb +55 -52
  13. data/lib/netzke/basepack/form_panel/fields.rb +4 -2
  14. data/lib/netzke/basepack/form_panel/javascripts/comma_list_cbg.js +13 -28
  15. data/lib/netzke/basepack/form_panel/javascripts/form_panel.js +61 -34
  16. data/lib/netzke/basepack/form_panel/javascripts/n_radio_group.js +4 -3
  17. data/lib/netzke/basepack/form_panel/javascripts/readonly_mode.js +10 -10
  18. data/lib/netzke/basepack/form_panel/services.rb +1 -1
  19. data/lib/netzke/basepack/grid_panel.rb +23 -17
  20. data/lib/netzke/basepack/grid_panel/columns.rb +116 -71
  21. data/lib/netzke/basepack/grid_panel/javascripts/advanced_search.js +5 -7
  22. data/lib/netzke/basepack/grid_panel/javascripts/check_column_fix.js +6 -0
  23. data/lib/netzke/basepack/grid_panel/javascripts/edit_in_form.js +18 -15
  24. data/lib/netzke/basepack/grid_panel/javascripts/event_handling.js +178 -0
  25. data/lib/netzke/basepack/grid_panel/javascripts/grid_panel.js +230 -454
  26. data/lib/netzke/basepack/grid_panel/javascripts/rows-dd.js +1 -0
  27. data/lib/netzke/basepack/grid_panel/record_form_window.rb +8 -8
  28. data/lib/netzke/basepack/grid_panel/services.rb +12 -15
  29. data/lib/netzke/basepack/paging_form_panel.rb +1 -82
  30. data/lib/netzke/basepack/paging_form_panel/javascripts/paging_form_panel.js +76 -0
  31. data/lib/netzke/basepack/query_builder.rb +4 -4
  32. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +7 -4
  33. data/lib/netzke/basepack/search_panel.rb +1 -1
  34. data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +27 -20
  35. data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +4 -4
  36. data/lib/netzke/basepack/search_window.rb +4 -4
  37. data/lib/netzke/basepack/simple_app.rb +36 -8
  38. data/lib/netzke/basepack/simple_app/javascripts/simple_app.js +27 -16
  39. data/lib/netzke/basepack/tab_panel/javascripts/tab_panel.js +1 -1
  40. data/lib/netzke/basepack/version.rb +2 -2
  41. data/lib/netzke/basepack/window.rb +2 -45
  42. data/lib/netzke/basepack/window/javascripts/window.js +18 -0
  43. data/lib/netzke/basepack/wrap_lazy_loaded.rb +1 -1
  44. data/locales/de.yml +79 -0
  45. data/locales/en.yml +0 -10
  46. data/netzke-basepack.gemspec +32 -19
  47. data/stylesheets/basepack.css +16 -16
  48. data/test/rails_app/.gitignore +1 -0
  49. data/test/rails_app/Gemfile +9 -5
  50. data/test/rails_app/Gemfile.lock +51 -49
  51. data/test/rails_app/app/components/author_form.rb +32 -0
  52. data/test/rails_app/app/components/book_form.rb +6 -4
  53. data/test/rails_app/app/components/book_grid.rb +4 -3
  54. data/test/rails_app/app/components/book_grid_loader.rb +2 -2
  55. data/test/rails_app/app/components/book_grid_with_custom_columns.rb +11 -5
  56. data/test/rails_app/app/components/book_grid_with_default_values.rb +1 -1
  57. data/test/rails_app/app/components/book_grid_with_extra_feedback.rb +11 -0
  58. data/test/rails_app/app/components/book_grid_with_extra_filters.rb +14 -0
  59. data/test/rails_app/app/components/book_grid_with_paging.rb +10 -0
  60. data/test/rails_app/app/components/book_grid_with_persistence.rb +6 -0
  61. data/test/rails_app/app/components/book_paging_form_panel.rb +3 -3
  62. data/test/rails_app/app/components/book_with_custom_primary_key_grid.rb +10 -0
  63. data/test/rails_app/app/components/form_without_model.rb +5 -4
  64. data/test/rails_app/app/components/paging_form_with_search.rb +3 -2
  65. data/test/rails_app/app/components/some_auth_app.rb +3 -3
  66. data/test/rails_app/app/components/some_border_layout.rb +2 -2
  67. data/test/rails_app/app/components/some_simple_app.rb +6 -5
  68. data/test/rails_app/app/components/window_component_loader.rb +11 -2
  69. data/test/rails_app/app/controllers/components_controller.rb +1 -1
  70. data/test/rails_app/app/models/book_with_custom_primary_key.rb +4 -0
  71. data/test/rails_app/config/initializers/netzke.rb +6 -0
  72. data/test/rails_app/config/locales/es.yml +0 -10
  73. data/test/rails_app/db/migrate/20110701070052_create_book_with_custom_primary_keys.rb +15 -0
  74. data/test/rails_app/db/schema.rb +12 -2
  75. data/test/rails_app/db/seeds.rb +21 -1
  76. data/test/rails_app/features/form_panel.feature +17 -17
  77. data/test/rails_app/features/grid_panel.feature +20 -4
  78. data/test/rails_app/features/grid_panel_with_custom_primary_key.feature +15 -0
  79. data/test/rails_app/features/paging_form_panel.feature +14 -0
  80. data/test/rails_app/features/search_in_grid.feature +2 -1
  81. data/test/rails_app/features/simple_app.feature +5 -0
  82. data/test/rails_app/features/step_definitions/accordion_steps.rb +1 -5
  83. data/test/rails_app/features/step_definitions/ext_steps.rb +16 -0
  84. data/test/rails_app/features/step_definitions/form_panel_steps.rb +10 -12
  85. data/test/rails_app/features/step_definitions/generic_steps.rb +12 -4
  86. data/test/rails_app/features/step_definitions/grid_panel_steps.rb +47 -32
  87. data/test/rails_app/features/tab_panel.feature +2 -2
  88. data/test/rails_app/spec/factories.rb +4 -0
  89. metadata +26 -13
  90. data/README.rdoc +0 -67
  91. data/from_05_to_06.rdoc +0 -2
  92. data/javascripts/feedback_ghost.js +0 -34
  93. data/test/rails_app/public/netzke/basepack/ts-checkbox.gif +0 -0
@@ -3,12 +3,9 @@
3
3
  if (this.searchWindow) {
4
4
  this.searchWindow.show();
5
5
  } else {
6
- this.loadComponent({name: 'search_form', callback: function(win){
6
+ this.loadNetzkeComponent({name: 'search_form', callback: function(win){
7
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
- }
8
+ win.show();
12
9
 
13
10
  win.items.first().on('apply', function(){
14
11
  win.onSearch();
@@ -18,8 +15,9 @@
18
15
  win.on('hide', function(){
19
16
  var query = win.getQuery();
20
17
  if (win.closeRes == 'search'){
21
- this.getStore().baseParams.query = Ext.encode(query);
22
- this.getStore().load();
18
+ var store = this.getStore(), proxy = store.getProxy();
19
+ proxy.extraParams.query = Ext.encode(query);
20
+ store.load();
23
21
  }
24
22
  el.toggle(query.length > 0); // toggle based on the state
25
23
  }, this);
@@ -0,0 +1,6 @@
1
+ Ext.override(Ext.ux.CheckColumn, {
2
+ processEvent: function() {
3
+ if (this.editable === false) return false;
4
+ else return this.callOverridden(arguments);
5
+ }
6
+ });
@@ -2,15 +2,16 @@
2
2
  onEditInForm: function(){
3
3
  var selModel = this.getSelectionModel();
4
4
  if (selModel.getCount() > 1) {
5
- var recordId = selModel.getSelected().id;
6
- this.loadComponent({name: "multi_edit_form",
5
+ var recordId = selModel.selected.first().getId();
6
+ this.loadNetzkeComponent({name: "multi_edit_form",
7
7
  params: {record_id: recordId},
8
8
  callback: function(w){
9
+ w.show();
9
10
  var form = w.items.first();
10
11
  form.on('apply', function(){
11
12
  var ids = [];
12
- selModel.each(function(r){
13
- ids.push(r.id);
13
+ selModel.selected.each(function(r){
14
+ ids.push(r.getId());
14
15
  });
15
16
  if (!form.baseParams) form.baseParams = {};
16
17
  form.baseParams.ids = Ext.encode(ids);
@@ -18,18 +19,19 @@
18
19
 
19
20
  w.on('close', function(){
20
21
  if (w.closeRes === "ok") {
21
- this.store.reload();
22
+ this.store.load();
22
23
  }
23
24
  }, this);
24
25
  }, scope: this});
25
26
  } else {
26
- var recordId = selModel.getSelected().id;
27
- this.loadComponent({name: "edit_form",
27
+ var recordId = selModel.selected.first().getId();
28
+ this.loadNetzkeComponent({name: "edit_form",
28
29
  params: {record_id: recordId},
29
- callback: function(form){
30
- form.on('close', function(){
31
- if (form.closeRes === "ok") {
32
- this.store.reload();
30
+ callback: function(w){
31
+ w.show();
32
+ w.on('close', function(){
33
+ if (w.closeRes === "ok") {
34
+ this.store.load();
33
35
  }
34
36
  }, this);
35
37
  }, scope: this});
@@ -37,10 +39,11 @@
37
39
  },
38
40
 
39
41
  onAddInForm: function(){
40
- this.loadComponent({name: "add_form", callback: function(form){
41
- form.on('close', function(){
42
- if (form.closeRes === "ok") {
43
- this.store.reload();
42
+ this.loadNetzkeComponent({name: "add_form", callback: function(w){
43
+ w.show();
44
+ w.on('close', function(){
45
+ if (w.closeRes === "ok") {
46
+ this.store.load();
44
47
  }
45
48
  }, this);
46
49
  }, scope: this});
@@ -0,0 +1,178 @@
1
+ {
2
+ // Handler for the 'add' button
3
+ onAddInline: function(){
4
+ // Note: default values are taken from the model's field's defaultValue property
5
+ var r = Ext.ModelManager.create({}, this.id);
6
+
7
+ r.isNew = true; // to distinguish new records
8
+
9
+ this.getStore().add(r);
10
+
11
+ this.tryStartEditing(r);
12
+ },
13
+
14
+ onDel: function() {
15
+ Ext.Msg.confirm(this.i18n.confirmation, this.i18n.areYouSure, function(btn){
16
+ if (btn == 'yes') {
17
+ var records = [];
18
+ var selection = this.getView().getSelectedNodes();
19
+ this.getSelectionModel().selected.each(function(r){
20
+ if (r.isNew) {
21
+ // this record is not know to server - simply remove from store
22
+ this.store.remove(r);
23
+ } else {
24
+ records.push(r.getId());
25
+ }
26
+ }, this);
27
+
28
+ if (records.length > 0){
29
+ if (!this.deleteMask) this.deleteMask = new Ext.LoadMask(this.getEl(), {msg: this.deleteMaskMsg});
30
+ this.deleteMask.show();
31
+ // call API
32
+ this.deleteData({records: Ext.encode(records)}, function(){
33
+ this.deleteMask.hide();
34
+ }, this);
35
+ }
36
+ }
37
+ }, this);
38
+ },
39
+
40
+ onApply: function(){
41
+ var newRecords = [],
42
+ updatedRecords = [],
43
+ store = this.getStore();
44
+
45
+ Ext.each(store.getUpdatedRecords().concat(store.getNewRecords()),
46
+ function(r) {
47
+ if (r.isNew) {
48
+ newRecords.push(r.data); // HACK: r.data seems private
49
+ } else {
50
+ updatedRecords.push(Ext.apply(r.getChanges(), {id:r.getId()}));
51
+ }
52
+ },
53
+ this);
54
+
55
+ if (newRecords.length > 0 || updatedRecords.length > 0) {
56
+ var params = {};
57
+
58
+ if (newRecords.length > 0) {
59
+ params.created_records = Ext.encode(newRecords);
60
+ }
61
+
62
+ if (updatedRecords.length > 0) {
63
+ params.updated_records = Ext.encode(updatedRecords);
64
+ }
65
+
66
+ if (this.getStore().getProxy().extraParams !== {}) {
67
+ params.base_params = Ext.encode(this.getStore().getProxy().extraParams);
68
+ }
69
+
70
+ this.postData(params);
71
+ }
72
+
73
+ },
74
+
75
+ // Handlers for tools
76
+ //
77
+
78
+ onRefresh: function() {
79
+ if (this.fireEvent('refresh', this) !== false) {
80
+ this.store.load();
81
+ }
82
+ },
83
+
84
+ // Event handlers
85
+ //
86
+
87
+ onColumnResize: function(ct, cl, width){
88
+ var index = ct.items.findIndex('id', cl.id);
89
+
90
+ this.resizeColumn({
91
+ index: index,
92
+ size: width
93
+ });
94
+ },
95
+
96
+ onColumnHide: function(ct, cl){
97
+ var index = ct.items.findIndex('id', cl.id);
98
+
99
+ this.hideColumn({
100
+ index:index,
101
+ hidden:true
102
+ });
103
+ },
104
+
105
+ onColumnShow: function(ct, cl){
106
+ var index = ct.items.findIndex('id', cl.id);
107
+
108
+ this.hideColumn({
109
+ index:index,
110
+ hidden:false
111
+ });
112
+ },
113
+
114
+ onColumnMove: function(ct, cl, oldIndex, newIndex){
115
+ this.moveColumn({
116
+ old_index: oldIndex,
117
+ new_index: newIndex
118
+ });
119
+ },
120
+
121
+ onItemContextMenu: function(grid, record, item, rowIndex, e){
122
+ e.stopEvent();
123
+ var coords = e.getXY();
124
+
125
+ if (!grid.getSelectionModel().isSelected(rowIndex)) {
126
+ grid.getSelectionModel().selectRow(rowIndex);
127
+ }
128
+
129
+ var menu = new Ext.menu.Menu({
130
+ items: this.contextMenu
131
+ });
132
+
133
+ menu.showAt(coords);
134
+ },
135
+
136
+ onAfterRowMove: function(dt, oldIndex, newIndex, records){
137
+ var ids = [];
138
+ // collect records ids
139
+ Ext.each(records, function(r){ids.push(r.id)});
140
+ // call GridPanel's API
141
+ this.moveRows({ids: Ext.encode(ids), new_index: newIndex});
142
+ },
143
+
144
+ // Other methods. TODO: revise
145
+ //
146
+
147
+ /* Exception handler. TODO: will responses with status 200 land here? */
148
+ loadExceptionHandler: function(proxy, response, operation){
149
+ this.netzkeFeedback(response.message);
150
+ // if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
151
+ // this.feedback(responseObject.flash);
152
+ // } else {
153
+ // if (error){
154
+ // this.feedback(error.message);
155
+ // } else {
156
+ // this.feedback(response.statusText);
157
+ // }
158
+ // }
159
+ },
160
+
161
+ // Inline editing of 1 row
162
+ onEdit: function(){
163
+ var row = this.getSelectionModel().selected.first();
164
+ if (row){
165
+ this.tryStartEditing(row);
166
+ }
167
+ },
168
+
169
+ // 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.
170
+ onDestroy: function(){
171
+ Netzke.classes.Basepack.GridPanel.superclass.onDestroy.call(this);
172
+
173
+ // Destroy the search window (here's the problem: we are not supposed to know it exists)
174
+ if (this.searchWindow) {
175
+ this.searchWindow.destroy();
176
+ }
177
+ }
178
+ }
@@ -5,116 +5,89 @@
5
5
 
6
6
  componentLoadMask: {msg: "Loading..."},
7
7
  deleteMaskMsg: "Deleting...",
8
+ multiSelect: true,
8
9
 
9
10
  initComponent: function(){
10
- this.plugins = []; // checkbox colums is a special case, being a plugin
11
-
12
- var filters = [];
13
11
  var metaColumn;
12
+ var fields = []; // field configs for the underlying data model
13
+
14
+ this.plugins = this.plugins || [];
15
+ this.features = this.features || [];
16
+
17
+ // Enable filters feature
18
+ this.features.push({
19
+ encode: true,
20
+ ftype: 'filters'
21
+ });
14
22
 
15
23
  // Run through columns and set up different configuration for each
16
24
  Ext.each(this.columns, function(c, i){
17
- // We will not use meta columns as actual columns (not even hidden) - only to create the records
18
- if (c.meta) {
19
- metaColumn = c;
20
- return;
21
- }
22
25
 
23
- // Apply default column config
24
- Ext.applyIf(c, this.defaultColumnConfig);
26
+ // Build the field configuration for this column
27
+ var fieldConfig = {name: c.name, defaultValue: c.defaultValue};
25
28
 
26
- // setting dataIndex separately
27
- c.dataIndex = c.name;
29
+ if (c.name !== '_meta') fieldConfig.type = this.fieldTypeForAttrType(c.attrType); // field type (grid editors need this to function well)
28
30
 
29
- // Automatically calculated default values
30
- if (!c.header) {c.header = c.label || c.name.humanize()}
31
+ if (c.attrType == 'datetime') {
32
+ fieldConfig.dateFormat = 'Y-m-d g:i:s'; // in this format we receive dates from the server
33
+ };
31
34
 
32
- // Set initial association values
33
- if (this.inlineData) {
34
- this.associationValues = this.inlineData.setAssociationValues;
35
- }
35
+ fields.push(fieldConfig);
36
36
 
37
- // normalize editor
38
- this.normalizeEditor(c);
37
+ // We will not use meta columns as actual columns (not even hidden) - only to create the records
38
+ if (c.meta) {
39
+ metaColumn = c;
40
+ return;
41
+ }
39
42
 
40
43
  // if comboboxOptions are provided, we render a combobox instead of textfield
41
- if (c.comboboxOptions && c.editor.xtype === "textfield") {
42
- c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
44
+ // if (c.comboboxOptions && c.editor.xtype === "textfield") {
45
+ // c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
46
+ // }
47
+
48
+ this.normalizeRenderer(c);
49
+
50
+ // Set rendeder for association columns (the one displaying associations by the specified method instead of id)
51
+ if (c.assoc) {
52
+ // Editor for association column
53
+ c.editor = Ext.apply({
54
+ parentId: this.id,
55
+ name: c.name,
56
+ selectOnFocus: true // ?
57
+ }, c.editor);
58
+
59
+ // Renderer for association column
60
+ this.normalizeAssociationRenderer(c);
43
61
  }
44
62
 
45
- // collect filters
46
- if (c.filterable){
47
- filters.push({type: this.filterTypeForAttrType(c.attrType), dataIndex: c.name});
63
+ if (c.editor) {
64
+ Ext.applyIf(c.editor, {selectOnFocus: true});
48
65
  }
49
66
 
50
- if (c.editor && c.editor.xtype == 'checkbox') {
51
- // Special case of checkbox column
52
- var plugin = new Ext.ux.grid.CheckColumn(c);
53
- this.plugins.push(plugin);
54
- this.columns[i] = plugin;
55
- } else {
56
- // a "normal" column, not a plugin
57
- if (!c.readOnly && !this.prohibitUpdate) {
58
- // c.editor contains complete config of the editor
59
- c.editor = Ext.apply({
60
- parentId: this.id,
61
- name: c.name,
62
- selectOnFocus: true
63
- }, c.editor);
64
- } else {
65
- c.editor = null;
66
- }
67
-
68
- this.normalizeRenderer(c);
69
-
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
- }
67
+ // Setting the default filter type
68
+ if (c.filterable && !c.filter) {
69
+ c.filter = {type: this.fieldTypeForAttrType(c.attrType)};
70
+ }
87
71
 
88
- return passedRenderer ? passedRenderer.call(this, renderedValue) : renderedValue;
89
- };
90
- }
72
+ // setting dataIndex
73
+ c.dataIndex = c.name;
91
74
 
92
- }
75
+ // HACK: somehow this is not set by Ext (while it should be)
76
+ if (c.xtype == 'datecolumn') c.format = c.format || Ext.util.Format.dateFormat;
93
77
 
94
78
  }, this);
95
79
 
96
- /* ... and done with columns */
80
+ /* ... and done with the columns */
97
81
 
98
- // Filters
99
- this.gridFilters = new Ext.ux.grid.GridFilters({filters:filters, encode: true});
100
- if (this.enableColumnFilters) {
101
- this.plugins.push(this.gridFilters);
102
- }
103
-
104
- // Create Ext.data.Record constructor specific for our particular column configuration
105
- this.recordConfig = [];
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);
114
- this.Row = Ext.data.Record.create(this.recordConfig);
82
+ // Define the model
83
+ Ext.define(this.id, {
84
+ extend: 'Ext.data.Model',
85
+ idProperty: this.pri, // Primary key
86
+ fields: fields
87
+ });
115
88
 
116
89
  // After we created the record (model), we can get rid of the meta column
117
- this.columns.remove(metaColumn);
90
+ Ext.Array.remove(this.columns, metaColumn);
118
91
 
119
92
  // Prepare column model config with columns in the correct order; columns out of order go to the end.
120
93
  var colModelConfig = [];
@@ -125,7 +98,6 @@
125
98
  Ext.each(this.columns, function(oc) {
126
99
  if (c.name === oc.name) {
127
100
  mainColConfig = Ext.apply({}, oc);
128
- // oc.inOrder = true;
129
101
  return false;
130
102
  }
131
103
  });
@@ -136,100 +108,98 @@
136
108
  // We don't need original columns any longer
137
109
  delete this.columns;
138
110
 
139
- // ... instead - define a custom column model
140
- this.colModel = new Ext.grid.ColumnModel(colModelConfig);
111
+ // ... instead, define own column model
112
+ this.columns = colModelConfig;
113
+
114
+ // DirectProxy that uses our Ext.direct provider
115
+ var proxy = {
116
+ type: 'direct',
117
+ directFn: Netzke.providers[this.id].getData,
118
+ reader: {
119
+ type: 'array',
120
+ root: 'data'
121
+ },
122
+ listeners: {
123
+ exception: {
124
+ fn: this.loadExceptionHandler,
125
+ scope: this
126
+ },
127
+ load: { // Netzke-introduced event; this will also be fired when an exception occurs.
128
+ fn: function(proxy, response, operation) {
129
+ // besides getting data into the store, we may also get commands to execute
130
+ response = response.result;
131
+ if (response) { // or did we have an exception?
132
+ Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
133
+ this.bulkExecute(response);
134
+ }
135
+ },
136
+ scope: this
137
+ }
138
+ }
139
+ }
140
+
141
+ this.store = Ext.create('store.store', {
142
+ model: this.id,
143
+ proxy: proxy,
144
+ data: this.inlineData && [this.inlineData.data] || [], // TODO: Inline data *might* contain commands to execute
145
+ pruneModifiedRecords: true,
146
+ remoteSort: true,
147
+ pageSize: this.rowsPerPage
148
+ });
149
+
150
+ // HACK: we must let the store now totalCount, but this property is not public (yet?)
151
+ this.store.totalCount = this.inlineData && this.inlineData[this.store.getProxy().getReader().totalProperty];
141
152
 
142
153
  // Drag'n'Drop
143
154
  if (this.enableRowsReordering){
144
155
  this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
145
- scrollable: true // enable scrolling support (default is false)
156
+ scrollable: true // enable scrolling support (default is false)
146
157
  });
147
158
  this.plugins.push(this.ddPlugin);
148
159
  }
149
160
 
150
- // DirectProxy that uses our Ext.direct provider
151
- this.proxy = new Ext.data.DirectProxy({directFn: Netzke.providers[this.id].getData});
152
-
153
- this.proxy.on('load', function (self, t, options) {
154
- // besides getting data into the store, we may also get commands to execute
155
- var response = t.result;
156
-
157
- // delete data-related properties
158
- Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
159
- this.bulkExecute(response);
160
- }, this);
161
-
162
- // Data store
163
- this.store = this.buildStore();
164
-
165
- // Normalize bottom bar
166
- this.bbar = (this.enablePagination) ? new Ext.PagingToolbar(Ext.copyTo({
167
- pageSize : this.rowsPerPage,
168
- items : this.bbar ? ["-"].concat(this.bbar) : [],
169
- store : this.store,
170
- emptyMsg: this.i18n.empty,
171
- displayInfo: true,
172
- plugins: this.gridFilters ? [this.gridFilters] : []
173
- }, this.i18n, 'emptyMsg,firstText,prevText,nextText,lastText,beforePageText,afterPageText,refreshText,displayMsg')) : this.bbar;
161
+ // Cell editing
162
+ if (!this.prohibitUpdate) {
163
+ this.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {pluginId: 'celleditor'}));
164
+ }
174
165
 
175
- // Selection model
176
- if (!this.sm) this.sm = new Ext.grid.RowSelectionModel();
166
+ // Paging toolbar
167
+ this.dockedItems = this.dockedItems || [];
168
+ this.dockedItems.push({
169
+ xtype: 'pagingtoolbar',
170
+ dock: 'bottom',
171
+ store: this.store,
172
+ items: ["-"].concat(this.bbar) // append the old bbar. TODO: get rid of it.
173
+ });
177
174
 
178
- this.view = this.buildView();
175
+ delete this.bbar;
179
176
 
180
177
  // Now let Ext.grid.EditorGridPanel do the rest (original initComponent)
181
- Netzke.classes.Basepack.GridPanel.superclass.initComponent.call(this);
182
-
183
- // Persistence-related events
184
- if (this.persistence) {
185
- // Hidden change event
186
- this.getColumnModel().on('hiddenchange', this.onColumnHiddenChange, this);
187
-
188
- // Inform the server part about column operations
189
- this.on('columnresize', this.onColumnResize, this);
190
- this.on('columnmove', this.onColumnMove, this);
191
- }
178
+ this.callParent();
192
179
 
193
180
  // Context menu
194
181
  if (this.contextMenu) {
195
- this.on('rowcontextmenu', this.onRowContextMenu, this);
182
+ this.on('itemcontextmenu', this.onItemContextMenu, this);
196
183
  }
197
184
 
198
185
  // Disabling/enabling editInForm button according to current selection
199
- if (this.enableEditInForm) {
200
- this.getSelectionModel().on('selectionchange', function(selModel){
186
+ if (this.enableEditInForm && !this.prohibitUpdate) {
187
+ this.getSelectionModel().on('selectionchange', function(selModel, selected){
201
188
  var disabled;
202
- if (!selModel.hasSelection()) {
189
+ if (selected.length === 0) { // empty?
203
190
  disabled = true;
204
191
  } else {
205
192
  // Disable "edit in form" button if new record is present in selection
206
- disabled = !selModel.each(function(r){
207
- if (r.isNew) { return false; }
193
+ Ext.each(selected, function(r){
194
+ if (r.isNew) { disabled = true; return false; }
208
195
  });
209
196
  };
210
197
  this.actions.editInForm.setDisabled(disabled);
211
198
  }, this);
212
199
  }
213
200
 
214
- // Load data AFTER the toolbar is bound to the store, which will provide for correct page number
215
- if (this.loadInlineData) {
216
- this.getStore().loadData(this.inlineData);
217
-
218
- // If rows per page specified, fake store.lastOptions as if the data was loaded
219
- // by PagingToolbar (for correct functionning of refresh tool and extended search)
220
- if (this.rowsPerPage) {
221
- this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
222
- }
223
-
224
- // inlineData may also contain commands (TODO: make it DRY, as this code repeats in multiple places...)
225
- // delete data-related properties
226
- Ext.each(['data', 'total', 'success'], function(property){ delete this.inlineData[property]; }, this);
227
- this.bulkExecute(this.inlineData);
228
- }
229
-
230
- // Process selectionchange event
201
+ // Process selectionchange event to enable/disable actions
231
202
  this.getSelectionModel().on('selectionchange', function(selModel){
232
- // enable/disable actions
233
203
  if (this.actions.del) this.actions.del.setDisabled(!selModel.hasSelection() || this.prohibitDelete);
234
204
  if (this.actions.edit) this.actions.edit.setDisabled(selModel.getCount() != 1 || this.prohibitUpdate);
235
205
  }, this);
@@ -239,246 +209,79 @@
239
209
  this.ddPlugin.on('afterrowmove', this.onAfterRowMove, this);
240
210
  }
241
211
 
242
- // GridView
212
+ // WIP: GridView
243
213
  this.getView().getRowClass = this.defaultGetRowClass;
244
214
 
245
215
  // 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.
246
216
  this.on('beforeedit', function(e){
247
- var column = this.getColumnModel().getColumnById(this.getColumnModel().getColumnId(e.column));
248
- if (column.assoc && column.getEditor().isXType('combo') && e.record.get('_meta')) {
249
- column.getEditor().getStore().loadData({
250
- data: [[e.record.get(e.field), e.record.get('_meta').associationValues[e.field]]]
251
- });
217
+ if (e.column.assoc && e.record.get('_meta')) {
218
+ var data = [e.record.get(e.field), e.record.get('_meta').associationValues[e.field]],
219
+ store = e.column.getEditor().store;
220
+ if (store.getCount() === 0) {
221
+ store.loadData([data]);
222
+ }
252
223
  }
253
224
  }, this);
254
- },
255
-
256
- buildStore: function() {
257
- return new Ext.data.Store({
258
- pruneModifiedRecords: true,
259
- proxy: this.proxy,
260
- reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
261
- remoteSort: true,
262
- listeners:{'loadexception':{
263
- fn:this.loadExceptionHandler,
264
- scope:this
265
- }}
266
- });
267
- },
268
225
 
269
- buildView: function() {
270
- return null;
226
+ this.on('afterrender', function() {
227
+ // Persistence-related events (afterrender to avoid blank event firing on render)
228
+ if (this.persistence) {
229
+ // Inform the server part about column operations
230
+ this.on('columnresize', this.onColumnResize, this);
231
+ this.on('columnmove', this.onColumnMove, this);
232
+ this.on('columnhide', this.onColumnHide, this);
233
+ this.on('columnshow', this.onColumnShow, this);
234
+ }
235
+ }, this);
271
236
  },
272
237
 
273
- filterTypeForAttrType: function(attrType){
238
+ fieldTypeForAttrType: function(attrType){
274
239
  var map = {
275
- integer :'Numeric',
276
- decimal :'Numeric',
277
- datetime:'Date',
278
- date :'Date',
279
- string :'String',
280
- 'boolean': 'Boolean'
240
+ integer : 'int',
241
+ decimal : 'float',
242
+ datetime : 'date',
243
+ date : 'date',
244
+ string : 'string',
245
+ text : 'string',
246
+ 'boolean' : 'boolean'
281
247
  };
282
- return map[attrType] || 'String';
248
+ return map[attrType] || 'string';
283
249
  },
284
250
 
285
- attrTypeEditorMap: {
286
- integer : "numberfield",
287
- "float" : "numberfield",
288
- "boolean": "checkbox",
289
- decimal : "numberfield",
290
- datetime : "datetimefield",
291
- date : "datefield",
292
- string : "textfield"
251
+ update: function(){
252
+ this.store.load();
293
253
  },
294
254
 
295
- setAssociationValues: function(assocObj) {
296
- this.associationValues = assocObj;
255
+ loadStoreData: function(data){
256
+ var dataRecords = this.getStore().getProxy().getReader().read(data);
257
+ this.getStore().loadData(dataRecords.records);
258
+ Ext.each(['data', 'total', 'success'], function(property){delete data[property];}, this);
259
+ this.bulkExecute(data);
297
260
  },
298
261
 
299
- onAdd: function(){
300
- var r = new this.Row();
301
- r.isNew = true; // to distinguish new records
302
- // r.set('id', r.id); // otherwise later r.get('id') returns empty string
303
- this.stopEditing();
304
- this.getStore().add(r);
305
-
306
- // Set default values
307
- this.getStore().fields.each(function(field){
308
- r.set(field.name, field.defaultValue);
309
- });
310
-
311
- this.tryStartEditing(this.store.indexOf(r));
312
- },
313
-
314
- onDel: function() {
315
- Ext.Msg.confirm(this.i18n.confirmation, this.i18n.areYouSure, function(btn){
316
- if (btn == 'yes') {
317
- var records = [];
318
- this.getSelectionModel().each(function(r){
319
- if (r.isNew) {
320
- // this record is not know to server - simply remove from store
321
- this.store.remove(r);
322
- } else {
323
- records.push(r.id);
324
- }
325
- }, this);
326
-
327
- if (records.length > 0){
328
- if (!this.deleteMask) this.deleteMask = new Ext.LoadMask(this.bwrap, {msg: this.deleteMaskMsg});
329
- this.deleteMask.show();
330
- // call API
331
- this.deleteData({records: Ext.encode(records)}, function(){
332
- this.deleteMask.hide();
333
- }, this);
334
- }
335
- }
336
- }, this);
337
- },
338
-
339
- onApply: function(){
340
- var newRecords = [];
341
- var updatedRecords = [];
342
- Ext.each(this.store.getModifiedRecords(),
343
- function(r) {
344
- if (r.isNew) {
345
- newRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
346
- } else {
347
- updatedRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
348
- }
349
- },
350
- this);
351
-
352
- if (newRecords.length > 0 || updatedRecords.length > 0) {
353
- var params = {};
354
-
355
- if (newRecords.length > 0) {
356
- params.created_records = Ext.encode(newRecords);
357
- }
358
-
359
- if (updatedRecords.length > 0) {
360
- params.updated_records = Ext.encode(updatedRecords);
361
- }
362
-
363
- if (this.store.baseParams !== {}) {
364
- params.base_params = Ext.encode(this.store.baseParams);
365
- }
366
-
367
- this.postData(params);
368
- }
369
-
370
- },
371
-
372
- // Handlers for tools
373
- //
374
-
375
- onRefresh: function() {
376
- if (this.fireEvent('refresh', this) !== false) {
377
- this.store.reload();
262
+ // Tries editing the first editable (i.e. not hidden, not read-only) sell
263
+ tryStartEditing: function(r){
264
+ var editableIndex = 0;
265
+ Ext.each(this.initialConfig.columns, function(c){
266
+ // skip columns that cannot be edited
267
+ if (!(c.hidden == true || c.editable == false || !c.editor || c.attrType == 'boolean')) {
268
+ return false;
378
269
  }
379
- },
380
-
381
- // Event handlers
382
- //
383
-
384
- onColumnResize: function(index, size){
385
- this.resizeColumn({
386
- index:index,
387
- size:size
388
- });
389
- },
390
-
391
- onColumnHiddenChange: function(cm, index, hidden){
392
- this.hideColumn({
393
- index:index,
394
- hidden:hidden
395
- });
396
- },
397
-
398
- onColumnMove: function(oldIndex, newIndex){
399
- this.moveColumn({
400
- old_index:oldIndex,
401
- new_index:newIndex
402
- });
403
-
404
- var newRecordConfig = [];
405
- Ext.each(this.getColumnModel().config, function(c){newRecordConfig.push({name: c.name})});
406
- delete this.Row; // old record constructor
407
- this.Row = Ext.data.Record.create(newRecordConfig);
408
- this.getStore().reader.recordType = this.Row;
409
- },
410
-
411
- onRowContextMenu: function(grid, rowIndex, e){
412
- e.stopEvent();
413
- var coords = e.getXY();
414
-
415
- if (!grid.getSelectionModel().isSelected(rowIndex)) {
416
- grid.getSelectionModel().selectRow(rowIndex);
417
- }
418
-
419
- var menu = new Ext.menu.Menu({
420
- items: this.contextMenu
421
- });
422
-
423
- menu.showAt(coords);
424
- },
425
-
426
- onAfterRowMove: function(dt, oldIndex, newIndex, records){
427
- var ids = [];
428
- // collect records ids
429
- Ext.each(records, function(r){ids.push(r.id)});
430
- // call GridPanel's API
431
- this.moveRows({ids:Ext.encode(ids), new_index: newIndex});
432
- },
433
-
434
- // Other methods
435
- //
270
+ editableIndex++;
271
+ });
436
272
 
437
- loadExceptionHandler: function(proxy, options, response, error){
438
- if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
439
- this.feedback(responseObject.flash);
440
- } else {
441
- if (error){
442
- this.feedback(error.message);
443
- } else {
444
- this.feedback(response.statusText);
445
- }
446
- }
273
+ if (editableIndex < this.initialConfig.columns.length) {this.getPlugin('celleditor').startEdit(r, this.columns[editableIndex]);}
447
274
  },
448
275
 
449
- update: function(){
450
- this.store.reload();
451
- },
452
-
453
- loadStoreData: function(data){
454
- this.store.loadData(data);
455
- Ext.each(['data', 'total', 'success'], function(property){delete data[property];}, this);
456
- this.bulkExecute(data);
457
- },
458
-
459
- // try editing the first editable (i.e. not hidden, not read-only) sell
460
- tryStartEditing: function(row){
461
- var editableIndex = 0;
462
- Ext.each(this.getColumnModel().config, function(c){
463
- // skip columns that cannot be edited
464
- if (!(c.hidden == true || c.editable == false || !c.editor || c.attrType == 'boolean')) {
465
- return false;
466
- }
467
- editableIndex++;
468
- });
469
-
470
- if (editableIndex < this.getColumnModel().config.length) {this.startEditing(row, editableIndex);}
471
- },
472
-
473
276
  // Called by the server side to update newly created records
474
277
  updateNewRecords: function(records){
475
- this.updateRecords(records);
476
- },
278
+ this.updateRecords(records);
279
+ },
477
280
 
478
281
  // Called by the server side to update modified records
479
282
  updateModRecords: function(records){
480
- this.updateRecords(records, true);
481
- },
283
+ this.updateRecords(records, true);
284
+ },
482
285
 
483
286
  // Updates modified or newly created records, by record ID
484
287
  // Example of the records argument (updated columns):
@@ -486,52 +289,52 @@
486
289
  // Example of the records argument (new columns, id autogenerated by Ext):
487
290
  // {"ext-record-200" => [1, 'value1', 'value2']}
488
291
  updateRecords: function(records, mod){
489
- if (!mod) {mod = false;}
490
- var modRecordsInGrid = [].concat(this.store.getModifiedRecords()); // there must be a better way to clone an array...
491
- // replace arrays of data in the args object with Ext.data.Record objects
492
- for (var k in records){
493
- records[k] = this.store.reader.readRecords({data:[records[k]]}).records[0];
494
- }
495
- // for each new record write the data returned by the server, and commit the record
496
- Ext.each(modRecordsInGrid, function(recordInGrid){
497
- if (mod ^ recordInGrid.isNew) {
498
- // if record is new, we access its id by "id", otherwise, the id is in the primary key column
499
- var recordId = recordInGrid.id;
500
- // new data that the server sent us to update this record (identified by the id)
501
- var newData = records[recordId];
502
-
503
- if (newData){
504
- for (var k in newData.data){
505
- recordInGrid.set(k, newData.get(k));
506
- }
507
-
508
- recordInGrid.isNew = false;
509
- recordInGrid.commit();
292
+ if (!mod) {mod = false;}
293
+ var modRecordsInGrid = [].concat(this.store.getUpdatedRecords()); // there must be a better way to clone an array...
294
+ // replace arrays of data in the args object with Ext.data.Record objects
295
+ for (var k in records){
296
+ records[k] = this.getStore().getProxy().getReader().read({data:[records[k]]}).records[0];
297
+ }
298
+ // for each new record write the data returned by the server, and commit the record
299
+ Ext.each(modRecordsInGrid, function(recordInGrid){
300
+ if (mod ^ recordInGrid.isNew) {
301
+ // if record is new, we access its id by "id", otherwise, the id is in the primary key column
302
+ var recordId = recordInGrid.getId();
303
+ // new data that the server sent us to update this record (identified by the id)
304
+ var newData = records[recordId];
305
+
306
+ if (newData){
307
+ for (var k in newData.data){
308
+ recordInGrid.set(k, newData.get(k));
510
309
  }
511
310
 
311
+ recordInGrid.isNew = false;
312
+ recordInGrid.commit();
512
313
  }
513
- }, this);
514
314
 
515
- // clear the selections
516
- this.getSelectionModel().clearSelections();
315
+ }
316
+ }, this);
517
317
 
518
- // check if there are still records with errors
519
- var modRecords = this.store.getModifiedRecords();
520
- if (modRecords.length == 0) {
521
- // if all records are accepted, reload the grid (so that eventual order/filtering is correct)
522
- this.store.reload();
318
+ // clear the selections
319
+ this.getSelectionModel().clearSelections();
523
320
 
524
- // ... and set default getRowClass function
525
- this.getView().getRowClass = this.defaultGetRowClass;
526
- } else {
527
- this.getView().getRowClass = function(r){
528
- return r.dirty ? "grid-dirty-record" : ""
529
- }
321
+ // check if there are still records with errors
322
+ var modRecords = this.store.getUpdatedRecords();
323
+ if (modRecords.length == 0) {
324
+ // if all records are accepted, reload the grid (so that eventual order/filtering is correct)
325
+ this.store.load();
326
+
327
+ // ... and set default getRowClass function
328
+ this.getView().getRowClass = this.defaultGetRowClass;
329
+ } else {
330
+ this.getView().getRowClass = function(r){
331
+ return r.dirty ? "grid-dirty-record" : ""
530
332
  }
333
+ }
531
334
 
532
- this.getView().refresh();
533
- this.getSelectionModel().fireEvent('selectionchange', this.getSelectionModel());
534
- },
335
+ this.getView().refresh();
336
+ this.getSelectionModel().fireEvent('selectionchange', this.getSelectionModel());
337
+ },
535
338
 
536
339
  defaultGetRowClass: function(r){
537
340
  return r.isNew ? "grid-dirty-record" : ""
@@ -543,18 +346,6 @@
543
346
  this.getSelectionModel().resumeEvents();
544
347
  },
545
348
 
546
- setDefaultColumnType: function(c) {
547
- if (c.xtype || c.renderer) return;
548
-
549
- switch (c.attrType) {
550
- case 'datetime': {
551
- c.xtype = 'datecolumn';
552
- c.format = c.format || "Y-m-d g:i:s";
553
- break;
554
- }
555
- }
556
- },
557
-
558
349
  // Normalizes the renderer for a column.
559
350
  // Renderer may be:
560
351
  // 1) a string that contains the name of the function to be used as renderer.
@@ -576,7 +367,7 @@
576
367
  var name, args = [];
577
368
 
578
369
  if ('string' === typeof c.renderer) {
579
- name = c.renderer;
370
+ name = c.renderer.camelize(true);
580
371
  } else {
581
372
  name = c.renderer[0];
582
373
  args = c.renderer.slice(1);
@@ -584,54 +375,39 @@
584
375
 
585
376
  // First check whether Ext.util.Format has it
586
377
  if (Ext.isFunction(Ext.util.Format[name])) {
587
- c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
378
+ c.renderer = Ext.Function.bind(Ext.util.Format[name], this, args, 1);
588
379
  } else if (Ext.isFunction(this[name])) {
589
380
  // ... then if our own class has it
590
- c.renderer = this[name].createDelegate(this, args, 1);
381
+ c.renderer = Ext.Function.bind(this[name], this, args, 1);
591
382
  } else {
592
383
  // ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
593
384
  eval("c.renderer = " + c.renderer + ";");
594
385
  }
595
386
  },
596
387
 
597
- normalizeEditor: function(c) {
598
- if (c.assoc) {
599
- } else {
600
- if (c.editor) {
601
- c.editor = Ext.isObject(c.editor) ? c.editor : {xtype:c.editor};
388
+ /*
389
+ Set a renderer that displayes association values instead of association record ID.
390
+ The association values are passed in the meta-column under associationValues hash.
391
+ */
392
+ normalizeAssociationRenderer: function(c) {
393
+ c.scope = this;
394
+ var passedRenderer = c.renderer; // renderer we got from normalizeRenderer
395
+ c.renderer = function(value, a, r, ri, ci){
396
+ var column = this.headerCt.items.getAt(ci),
397
+ editor = column.getEditor && column.getEditor(),
398
+ // HACK: using private property 'store'
399
+ recordFromStore = editor && editor.isXType('combobox') && editor.store.findRecord('field1', value),
400
+ renderedValue;
401
+
402
+ if (recordFromStore) {
403
+ renderedValue = recordFromStore.get('field2');
404
+ } else if (c.assoc && r.get('_meta')) {
405
+ renderedValue = r.get('_meta').associationValues[c.name] || value;
602
406
  } else {
603
- c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
407
+ renderedValue = value;
604
408
  }
605
- }
606
409
 
607
- },
608
-
609
- onEdit: function(){
610
- var row = this.getSelectionModel().getSelected();
611
- if (row){
612
- this.tryStartEditing(this.store.indexOf(row));
613
- }
614
- },
615
-
616
- // 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.
617
- onDestroy: function(){
618
- Netzke.classes.Basepack.GridPanel.superclass.onDestroy.call(this);
619
-
620
- // Destroy the search window (here's the problem: we are not supposed to know it exists)
621
- if (this.searchWindow) {
622
- this.searchWindow.destroy();
623
- }
410
+ return passedRenderer ? passedRenderer.call(this, renderedValue) : renderedValue;
411
+ };
624
412
  }
625
-
626
- // :reorder_columns => <<-END_OF_JAVASCRIPT.l,
627
- // function(columns){
628
- // columnsInNewShipment = [];
629
- // Ext.each(columns, function(c){
630
- // columnsInNewShipment.push({name:c});
631
- // });
632
- // newRecordType = Ext.data.Record.create(columnsInNewShipment);
633
- // 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
634
- // }
635
- // END_OF_JAVASCRIPT
636
-
637
413
  }