zena 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. data/History.txt +38 -1
  2. data/app/controllers/documents_controller.rb +7 -5
  3. data/app/controllers/nodes_controller.rb +47 -6
  4. data/app/controllers/user_sessions_controller.rb +12 -3
  5. data/app/controllers/virtual_classes_controller.rb +8 -2
  6. data/app/models/acl.rb +5 -2
  7. data/app/models/cached_page.rb +5 -5
  8. data/app/models/column.rb +27 -4
  9. data/app/models/group.rb +1 -1
  10. data/app/models/node.rb +106 -24
  11. data/app/models/note.rb +2 -1
  12. data/app/models/relation.rb +9 -4
  13. data/app/models/relation_proxy.rb +2 -2
  14. data/app/models/role.rb +12 -5
  15. data/app/models/site.rb +10 -9
  16. data/app/models/skin.rb +8 -0
  17. data/app/models/string_hash.rb +65 -0
  18. data/app/models/text_document.rb +1 -1
  19. data/app/models/user.rb +2 -0
  20. data/app/models/virtual_class.rb +43 -10
  21. data/app/views/comments/create.rjs +1 -32
  22. data/app/views/comments/edit.rjs +1 -1
  23. data/app/views/comments/update.rjs +1 -1
  24. data/app/views/documents/show.rhtml +1 -1
  25. data/app/views/groups/_form.rhtml +7 -0
  26. data/app/views/groups/_li.rhtml +1 -1
  27. data/app/views/nodes/500.html +2 -1
  28. data/app/views/nodes/destroy.rjs +2 -0
  29. data/app/views/sites/jobs.erb +2 -3
  30. data/app/views/templates/document_create_tabs/_file.rhtml +1 -1
  31. data/app/views/templates/document_create_tabs/_import.rhtml +4 -1
  32. data/app/views/templates/document_create_tabs/_template.rhtml +3 -0
  33. data/app/views/templates/document_create_tabs/_text_document.rhtml +3 -0
  34. data/app/views/versions/custom_tab.rhtml +1 -1
  35. data/app/views/versions/edit.rhtml +1 -1
  36. data/bricks/acls/lib/bricks/acls.rb +3 -3
  37. data/bricks/acls/zena/test/unit/acl_test.rb +15 -0
  38. data/bricks/fs_skin/lib/bricks/fs_skin.rb +190 -0
  39. data/bricks/fs_skin/zena/init.rb +1 -0
  40. data/bricks/fs_skin/zena/migrate/20110702010330_add_fs_skin_to_idx_templates.rb +12 -0
  41. data/bricks/{static → fs_skin}/zena/skins/blog/Image-edit.zafu +0 -0
  42. data/bricks/{static → fs_skin}/zena/skins/blog/Image.zafu +0 -0
  43. data/bricks/{static → fs_skin}/zena/skins/blog/Node-+index.zafu +0 -0
  44. data/bricks/{static → fs_skin}/zena/skins/blog/Node-+notFound.zafu +0 -0
  45. data/bricks/{static → fs_skin}/zena/skins/blog/Node-+search.zafu +0 -0
  46. data/bricks/{static → fs_skin}/zena/skins/blog/Node.zafu +1 -1
  47. data/bricks/{static → fs_skin}/zena/skins/blog/Post.zafu +0 -0
  48. data/bricks/{static → fs_skin}/zena/skins/blog/Project--kml.zafu +0 -0
  49. data/bricks/{static → fs_skin}/zena/skins/blog/Project.zafu +0 -0
  50. data/bricks/{static → fs_skin}/zena/skins/blog/comments.zafu +0 -0
  51. data/bricks/{static → fs_skin}/zena/skins/blog/dict.yml +0 -0
  52. data/bricks/{static → fs_skin}/zena/skins/blog/img/dateBg.jpg +0 -0
  53. data/bricks/{static → fs_skin}/zena/skins/blog/img/header.png +0 -0
  54. data/bricks/{static → fs_skin}/zena/skins/blog/img/mapPin.png +0 -0
  55. data/bricks/{static → fs_skin}/zena/skins/blog/img/menu.gif +0 -0
  56. data/bricks/{static → fs_skin}/zena/skins/blog/img/menuover.gif +0 -0
  57. data/bricks/{static → fs_skin}/zena/skins/blog/img/style.css +0 -0
  58. data/bricks/fs_skin/zena/tasks.rb +26 -0
  59. data/bricks/{static/zena/test/integration/static_integration_test.rb → fs_skin/zena/test/integration/fs_skin_integration_test.rb} +6 -6
  60. data/bricks/fs_skin/zena/test/unit/fs_skin_test.rb +33 -0
  61. data/bricks/grid/lib/bricks/grid.rb +4 -3
  62. data/bricks/tags/lib/bricks/tags.rb +1 -7
  63. data/bricks/zena/zena/migrate/20120605091558_add_ssl_login_to_site.rb +7 -0
  64. data/bricks/zena/zena/migrate/20120630123551_add_auto_publish_to_group.rb +9 -0
  65. data/config/bricks.yml +3 -3
  66. data/config/gems.yml +2 -3
  67. data/lib/tasks/zena.rake +7 -3
  68. data/lib/zafu.rb +7 -0
  69. data/lib/zafu/all.rb +21 -0
  70. data/lib/zafu/compiler.rb +7 -0
  71. data/lib/zafu/controller_methods.rb +58 -0
  72. data/lib/zafu/handler.rb +57 -0
  73. data/lib/zafu/info.rb +4 -0
  74. data/lib/zafu/markup.rb +309 -0
  75. data/lib/zafu/mock_helper.rb +42 -0
  76. data/lib/zafu/node_context.rb +203 -0
  77. data/lib/zafu/ordered_hash.rb +53 -0
  78. data/lib/zafu/parser.rb +676 -0
  79. data/lib/zafu/parsing_rules.rb +382 -0
  80. data/lib/zafu/process/ajax.rb +530 -0
  81. data/lib/zafu/process/conditional.rb +92 -0
  82. data/lib/zafu/process/context.rb +186 -0
  83. data/lib/zafu/process/forms.rb +143 -0
  84. data/lib/zafu/process/html.rb +186 -0
  85. data/lib/zafu/process/ruby_less_processing.rb +321 -0
  86. data/lib/zafu/security.rb +15 -0
  87. data/lib/zafu/template.rb +25 -0
  88. data/lib/zafu/test_helper.rb +19 -0
  89. data/lib/zafu/view_methods.rb +6 -0
  90. data/lib/zena.rb +1 -1
  91. data/lib/zena/acts/enrollable.rb +1 -1
  92. data/lib/zena/app.rb +4 -17
  93. data/lib/zena/console.rb +18 -1
  94. data/lib/zena/core_ext/file_utils.rb +13 -1
  95. data/lib/zena/core_ext/fixnum.rb +4 -0
  96. data/lib/zena/core_ext/float.rb +7 -0
  97. data/lib/zena/deploy.rb +4 -2
  98. data/lib/zena/deploy/app_init.rhtml +2 -1
  99. data/lib/zena/deploy/database.rhtml +1 -1
  100. data/lib/zena/info.rb +1 -1
  101. data/lib/zena/parser/zazen_rules.rb +4 -4
  102. data/lib/zena/routes.rb +1 -1
  103. data/lib/zena/test_controller.rb +1 -1
  104. data/lib/zena/use.rb +14 -1
  105. data/lib/zena/use/action.rb +4 -2
  106. data/lib/zena/use/ajax.rb +86 -38
  107. data/lib/zena/use/authlogic.rb +16 -1
  108. data/lib/zena/use/calendar.rb +37 -17
  109. data/lib/zena/use/conditional.rb +2 -2
  110. data/lib/zena/use/context.rb +30 -9
  111. data/lib/zena/use/dates.rb +39 -3
  112. data/lib/zena/use/display.rb +6 -19
  113. data/lib/zena/use/forms.rb +100 -79
  114. data/lib/zena/use/i18n.rb +40 -16
  115. data/lib/zena/use/query_builder.rb +0 -6
  116. data/lib/zena/use/query_node.rb +17 -4
  117. data/lib/zena/use/relations.rb +1 -3
  118. data/lib/zena/use/rendering.rb +10 -8
  119. data/lib/zena/use/scope_index.rb +5 -1
  120. data/lib/zena/use/search.rb +2 -1
  121. data/lib/zena/use/urls.rb +82 -77
  122. data/lib/zena/use/workflow.rb +12 -4
  123. data/lib/zena/use/zafu_safe_definitions.rb +37 -9
  124. data/lib/zena/use/zafu_templates.rb +49 -20
  125. data/lib/zena/use/zazen.rb +6 -2
  126. data/locale/it/LC_MESSAGES/zena.mo +0 -0
  127. data/locale/it/zena.mo +0 -0
  128. data/locale/it/zena.po +1982 -0
  129. data/public/images/arrow_back.png +0 -0
  130. data/public/images/remove_tag.png +0 -0
  131. data/public/javascripts/grid.js +800 -199
  132. data/public/javascripts/window.js +1 -1
  133. data/public/javascripts/zena.js +130 -21
  134. data/public/stylesheets/grid.css +11 -2
  135. data/public/stylesheets/zena.css +2 -1
  136. data/test/custom_queries/complex.host.yml +5 -0
  137. data/test/fixtures/files/TestNode.zafu +36 -0
  138. data/test/functional/nodes_controller_test.rb +18 -1
  139. data/test/integration/zafu_compiler/action.yml +2 -2
  140. data/test/integration/zafu_compiler/ajax.yml +44 -26
  141. data/test/integration/zafu_compiler/asset.yml +12 -2
  142. data/test/integration/zafu_compiler/basic.yml +0 -16
  143. data/test/integration/zafu_compiler/calendar.yml +6 -6
  144. data/test/integration/zafu_compiler/complex_ok.yml +23 -1
  145. data/test/integration/zafu_compiler/conditional.yml +5 -5
  146. data/test/integration/zafu_compiler/context.yml +6 -5
  147. data/test/integration/zafu_compiler/dates.yml +23 -2
  148. data/test/integration/zafu_compiler/display.yml +46 -2
  149. data/test/integration/zafu_compiler/errors.yml +2 -2
  150. data/test/integration/zafu_compiler/eval.yml +35 -7
  151. data/test/integration/zafu_compiler/forms.yml +47 -13
  152. data/test/integration/zafu_compiler/i18n.yml +2 -2
  153. data/test/integration/zafu_compiler/meta.yml +35 -1
  154. data/test/integration/zafu_compiler/query.yml +23 -4
  155. data/test/integration/zafu_compiler/relations.yml +10 -6
  156. data/test/integration/zafu_compiler/roles.yml +4 -4
  157. data/test/integration/zafu_compiler/rubyless.yml +11 -1
  158. data/test/integration/zafu_compiler/safe_definitions.yml +23 -5
  159. data/test/integration/zafu_compiler/security.yml +10 -6
  160. data/test/integration/zafu_compiler/urls.yml +23 -6
  161. data/test/integration/zafu_compiler/zafu_attributes.yml +1 -1
  162. data/test/integration/zafu_compiler/zazen.yml +14 -0
  163. data/test/selenium/Add/add3.rsel +8 -8
  164. data/test/selenium/Destroy/0setup.rsel +12 -0
  165. data/test/selenium/Destroy/destroy1.rsel +16 -0
  166. data/test/selenium/Edit/edit2.rsel +9 -9
  167. data/test/selenium/Edit/edit5.rsel +9 -9
  168. data/test/selenium/Edit/edit6.rsel +9 -9
  169. data/test/selenium/Form/form4.rsel +17 -0
  170. data/test/selenium/Toggle/toggle1.rsel +2 -0
  171. data/test/selenium/Toggle/toggle2.rsel +18 -0
  172. data/test/sites/zena/columns.yml +3 -0
  173. data/test/sites/zena/versions.yml +7 -0
  174. data/test/unit/cached_page_test.rb +13 -13
  175. data/test/unit/column_test.rb +26 -0
  176. data/test/unit/node_test.rb +16 -1
  177. data/test/unit/project_test.rb +6 -1
  178. data/test/unit/relation_test.rb +1 -1
  179. data/test/unit/role_test.rb +1 -1
  180. data/test/unit/string_hash_test.rb +30 -0
  181. data/test/unit/virtual_class_test.rb +31 -17
  182. data/test/unit/zafu_markup_test.rb +414 -0
  183. data/test/unit/zafu_node_context_test.rb +375 -0
  184. data/test/unit/zafu_ordered_hash_test.rb +69 -0
  185. data/test/unit/zena/acts/enrollable_test.rb +1 -1
  186. data/test/unit/zena/parser/zafu_asset.yml +0 -10
  187. data/test/unit/zena/parser/zazen.yml +1 -1
  188. data/test/unit/zena/parser_test.rb +1 -72
  189. data/test/unit/zena/use/dates_test.rb +1 -1
  190. data/test/unit/zena/use/rendering_test.rb +24 -7
  191. data/test/unit/zena/use/scope_index_test.rb +17 -0
  192. data/test/unit/zena/use/zazen_test.rb +2 -1
  193. data/zena.gemspec +71 -37
  194. metadata +104 -83
  195. data/app/views/nodes/destroy.erb +0 -0
  196. data/bricks/static/lib/bricks/static.rb +0 -151
  197. data/bricks/static/zena/init.rb +0 -1
  198. data/bricks/static/zena/migrate/20110702010330_add_static_to_idx_templates.rb +0 -12
  199. data/bricks/static/zena/test/unit/static_test.rb +0 -33
  200. data/lib/zena/parser/zafu_rules.rb +0 -244
  201. data/lib/zena/parser/zafu_tags.rb +0 -198
  202. data/lib/zena/parser/zena_rules.rb +0 -23
@@ -3,50 +3,127 @@ Grid = {
3
3
  grid_c: 0,
4
4
  };
5
5
 
6
- Grid.changed = function(cell, value) {
7
- cell.innerHTML = value;
8
- var row = cell.up('tr');
9
- var table = row.up('table');
10
- if (cell.prev_value == value) return;
11
- if (cell.orig_value == value) {
12
- cell.removeClassName('changed');
6
+ if (Prototype.Browser.WebKit) {
7
+ Grid.default_input = "<input type='text' value=''/>"
8
+ Grid.paste_mode = 'redirect'
9
+ } else {
10
+ Grid.default_input = '<textarea></textarea>'
11
+ Grid.paste_mode = 'inline'
12
+ }
13
+
14
+ Grid.log = function(what, msg) {
15
+ var log = $('log')
16
+ if (typeof(msg) != 'string') msg = Object.toJSON(msg)
17
+ log.innerHTML = log.innerHTML + '<br/><b>' + what + '</b> ' + msg
18
+ }
19
+
20
+ Grid.changed = function(cell, val, prev, skip_html) {
21
+ if (!skip_html) {
22
+ if (val.value == val.show) {
23
+ cell.innerHTML = val.value
24
+ } else {
25
+ cell.innerHTML = val.show
26
+ cell.setAttribute('data-v', val.value)
27
+ }
28
+ }
29
+ var row = cell.up('tr')
30
+ var table = row.up('table')
31
+ var grid = table.grid
32
+ if (prev.value == val.value) return;
33
+ if (cell.orig_value == val.value) {
34
+ cell.removeClassName('changed')
13
35
  if (row.select('.changed').length == 0) {
14
- row.removeClassName('changed');
36
+ row.removeClassName('changed')
15
37
  }
16
38
  } else {
17
- cell.addClassName('changed');
18
- row.addClassName('changed');
39
+ cell.addClassName('changed')
40
+ row.addClassName('changed')
19
41
  }
20
- var pos = Grid.pos(cell);
42
+ var pos = Grid.pos(cell)
21
43
 
22
- var attr, id;
23
- if (table.grid.attr_name) {
44
+ var attr, id
45
+ if (grid.attr_name) {
24
46
  attr = pos;
25
47
  id = Grid.pos(row) - 1;
26
48
  } else {
27
- attr = table.grid.attr[pos];
49
+ attr = grid.attr[pos];
28
50
  id = row.id;
51
+ if (!id) {
52
+ // Prepare for create
53
+ id = Grid.buildObj(grid, row)
54
+ }
29
55
  }
30
56
 
31
57
  var change = {
32
- id: id
58
+ id: id,
59
+ _old: prev,
33
60
  };
34
- change[attr] = value;
61
+ change[attr] = val;
35
62
  var table = row.up('table');
36
- table.grid.changes.push(change);
63
+ grid.changes.push(change);
37
64
  }
38
65
 
39
- Grid.close_cell = function(event) {
40
- var input = event.element();
41
- var cell = input.up();
42
- var table = event.findElement('table');
43
- cell.removeClassName('input');
44
- // simple case
45
- Grid.changed(cell, input.value.strip());
46
- if (table.grid.input) {
47
- // single attribute table, serialize in input field
48
- table.grid.input.value = Grid.serialize(table);
66
+ // Push a row as a new object in changes.
67
+ Grid.buildObj = function(grid, row) {
68
+ // Set a temporary id
69
+ grid.counter++
70
+ var id = 'new_' + grid.id + '_' + grid.counter
71
+ row.id = id
72
+ row.addClassName('new')
73
+ var base = {
74
+ id: id,
75
+ _new: {value:true}
76
+ }
77
+ // Add all default attributes
78
+ grid.defaults.each(function(pair) {
79
+ base[pair.key] = pair.value
80
+ })
81
+ // Add all attributes
82
+ var cells = row.childElements()
83
+ for (var i = 0; i < cells.length - 1; i++) {
84
+ var cell = cells[i]
85
+ var attr = grid.attr[i]
86
+ if (attr) {
87
+ // A readonly cell *MUST* have data-v set or it is ignored.
88
+ if (!cell.getAttribute('data-v') && Grid.isReadOnly(cell)) continue
89
+ var val = Grid.getValue(cell)
90
+ if (val.value.strip() == '') {
91
+ val = base[attr] || val
92
+ } else {
93
+ base[attr] = val
94
+ }
95
+ if (cell.innerHTML.strip() == '') {
96
+ cell.innerHTML = val.show
97
+ }
49
98
  }
99
+ }
100
+ grid.changes.push(base)
101
+ return id
102
+ }
103
+
104
+ Grid.closeCell = function(e) {
105
+ if (Grid.in_paste) return
106
+ var cell = e.tagName ? e : e.element().up()
107
+ var table = cell.up('table')
108
+ var prev = cell.prev_value
109
+ var val = Grid.getValue(cell)
110
+
111
+ if (table.grid.list_name) {
112
+ if (val.value == 'on') {
113
+ cell.addClassName('on')
114
+ } else {
115
+ cell.removeClassName('on')
116
+ }
117
+ } else {
118
+ cell.removeClassName('input')
119
+ }
120
+
121
+ Grid.changed(cell, val, prev)
122
+
123
+ if (table.grid.input) {
124
+ // single attribute table, serialize in input field
125
+ table.grid.input.value = Grid.serialize(table)
126
+ }
50
127
  }
51
128
 
52
129
  Grid.pos = function(elem) {
@@ -65,15 +142,27 @@ Grid.paste = function(event) {
65
142
  var tbody = table.childElements()[0];
66
143
  var rows = tbody.childElements();
67
144
  var cell_offset = Grid.pos(start_cell);
68
- // Redirect paste inside the paste textarea
69
- $(document.body).insert({
70
- bottom: "<textarea style='position:fixed; top:0; left:10100px;' id='grid_p_" + table.grid.id + "'></textarea>"
71
- });
72
- var paster = $("grid_p_" + table.grid.id);
73
- paster.focus();
145
+
146
+ var paster
147
+ if (Grid.paste_mode == 'redirect') {
148
+ // Redirect paste inside the paste textarea
149
+ $(document.body).insert({
150
+ bottom: "<textarea style='position:fixed; top:0; left:10100px;' id='grid_p_" + table.grid.id + "'></textarea>"
151
+ });
152
+ paster = $("grid_p_" + table.grid.id);
153
+ Grid.in_paste = true // prevent original input blur
154
+ paster.focus();
155
+ Grid.in_paste = false
156
+ }
74
157
  setTimeout(function() {
75
- var text = paster.value;
76
- paster.remove();
158
+ var text
159
+ if (Grid.paste_mode == 'redirect') {
160
+ text = paster.value
161
+ paster.remove()
162
+ input.focus()
163
+ } else {
164
+ text = input.value
165
+ }
77
166
 
78
167
  var lines = text.strip().split(/\r\n|\r|\n/);
79
168
  for (var i = 0; i < lines.length; i++) {
@@ -84,6 +173,7 @@ Grid.paste = function(event) {
84
173
  input.value = lines[0][0];
85
174
  } else {
86
175
  // copy/paste from spreadsheet
176
+ table.grid.changes.push('start')
87
177
  var should_create = table.grid.input && true;
88
178
  for (var i = 0; i < lines.length; i++) {
89
179
  // foreach line
@@ -92,7 +182,7 @@ Grid.paste = function(event) {
92
182
  if (!row) {
93
183
  if (!should_create) break;
94
184
  // create a new row
95
- Grid.add_row(table, rows[row_offset + i - 1]);
185
+ Grid.addRow(table, rows[row_offset + i - 1]);
96
186
  rows = tbody.select('tr');
97
187
  row = rows[row_offset + i];
98
188
  }
@@ -104,24 +194,41 @@ Grid.paste = function(event) {
104
194
  if (!cell) {
105
195
  if (!should_create) break;
106
196
  // create a new cell
107
- Grid.add_col(table, cells[cell_offset + j - 1]);
197
+ Grid.addCol(table, cells[cell_offset + j - 1]);
108
198
  cells = row.childElements(); cells.pop();
109
199
  cell = cells[cell_offset + j];
110
200
  }
111
- Grid.changed(cell, tabs[j]);
201
+ var val = {value:tabs[j], show:tabs[j]}
202
+ if (i==0 && j==0) {
203
+ input.value = val.value
204
+ Grid.changed(cell, val, cell.prev_value, true)
205
+ cell.prev_value = val
206
+ } else if (!Grid.isReadOnly(cell)) {
207
+ var prev = Grid.getValue(cell)
208
+ Grid.changed(cell, val, prev)
209
+ }
112
210
  }
113
211
  }
212
+ table.grid.changes.push('end')
114
213
  }
115
- Grid.open_cell(start_cell);
116
- }, 100);
214
+ if (table.grid.input) {
215
+ // single attribute table, serialize in input field
216
+ table.grid.input.value = Grid.serialize(table)
217
+ }
218
+ }, 0)
117
219
  return true;
118
220
  }
119
221
 
120
222
  Grid.keydown = function(event) {
121
- var input = event.element();
122
- var key = event.keyCode;
123
- var cell = input.up();
124
- if (key == 39 || (key == 9 && !event.shiftKey)) {
223
+ var input = event.element()
224
+ var key = event.keyCode
225
+ var cell = input.up()
226
+ var grid = cell.up('table').grid
227
+ if (grid.keydown && grid.keydown(event, key)) {
228
+ event.stop()
229
+ return false
230
+ }
231
+ if ((false && key == 39) || (key == 9 && !event.shiftKey)) {
125
232
  // tab + key right
126
233
  var next = cell.nextSiblings()[0];
127
234
  if (!next || next.hasClassName('action')) {
@@ -132,24 +239,32 @@ Grid.keydown = function(event) {
132
239
  }
133
240
  next = row.childElements()[0];
134
241
  }
135
- Grid.open_cell(next);
242
+ Grid.openCell(next);
136
243
  event.stop();
137
- } else if (key == 37 || (key == 9 && event.shiftKey)) {
244
+ } else if ((false && key == 37) || (key == 9 && event.shiftKey)) {
245
+ // shift-tab + left key
138
246
  var prev = cell.previousSiblings()[0];
139
247
  if (!prev) {
140
248
  // wrap back around on shift+tab
141
- var row = cell.up('tr').previousSiblings()[0];
142
- if (!row || row.childElements()[0].tagName == 'TH') {
249
+ var row = cell.up('tr').previousSiblings()[0]
250
+ if (!row || row.hasClassName('action')) {
143
251
  row = cell.up('tbody').childElements().last();
144
252
  }
145
253
  prev = row.childElements().last();
146
254
  if (prev.hasClassName('action'))
147
255
  prev = prev.previousSiblings()[0];
148
256
  }
149
- Grid.open_cell(prev);
257
+ Grid.openCell(prev);
150
258
  event.stop();
151
- } else if (key == 40 || key == 13) {
152
- // find position
259
+ } else if ((key == 40 && event.altKey) || (key == 13 && !event.shiftKey)) {
260
+ // down
261
+ if (event.altKey) {
262
+ Grid.copy(cell, 'down')
263
+ event.stop()
264
+ return false
265
+ } else if (cell.childElements().first().tagName == 'SELECT' && event.shiftKey) {
266
+ return
267
+ }
153
268
  var pos = Grid.pos(cell);
154
269
  // go to next row
155
270
  var crow = cell.up();
@@ -157,19 +272,24 @@ Grid.keydown = function(event) {
157
272
  // find elem
158
273
  if (!row) {
159
274
  // open new row
160
- Grid.add_row(crow.up(), cell.up());
161
- row = crow.nextSiblings().first();
162
- var next = row.childElements()[0];
163
- setTimeout(function() {
164
- Grid.open_cell(next);
165
- }, 100);
275
+ if (crow.up('table').grid.add) {
276
+ Grid.addRow(crow.up('table'), cell.up());
277
+ row = crow.nextSiblings().first();
278
+ var next = row.childElements()[0];
279
+ setTimeout(function() {
280
+ Grid.openCell(next);
281
+ }, 100);
282
+ }
166
283
  } else {
167
284
  next = row.childElements()[pos];
168
- Grid.open_cell(next);
285
+ Grid.openCell(next);
169
286
  }
170
287
  event.stop();
171
- } else if (key == 38) {
172
- // go to prev row
288
+ } else if ((false && key == 38) || (key == 13 && event.shiftKey)) {
289
+ // up
290
+ if (cell.childElements().first().tagName == 'SELECT' && event.shiftKey) {
291
+ return
292
+ }
173
293
  var row = cell.up();
174
294
  if (Grid.pos(row) == 1) {
175
295
  // stop
@@ -178,65 +298,165 @@ Grid.keydown = function(event) {
178
298
  // move up
179
299
  row = row.previousSiblings().first();
180
300
  var next = row.childElements()[pos];
181
- Grid.open_cell(next);
301
+ Grid.openCell(next);
182
302
  }
183
303
  event.stop();
184
304
  }
185
305
  return false;
186
306
  }
187
307
 
188
- Grid.open_cell = function(cell) {
189
- if (cell.hasClassName('input')) return;
190
- var value = cell.innerHTML;
308
+ Grid.isReadOnly = function(cell) {
309
+ return cell.select('a').length > 0 || cell.getAttribute('data-m') == 'r'
310
+ }
191
311
 
192
- if (!cell.orig_value) cell.orig_value = value;
193
- cell.prev_value = value;
312
+ Grid.openCell = function(cell) {
313
+ if (cell.hasClassName('input') || Grid.isReadOnly(cell)) return;
314
+ var val = Grid.getValue(cell)
315
+ cell.prev_value = val;
194
316
 
195
- var w = cell.getWidth() - 5;
196
- var h = cell.getHeight() - 5;
197
- cell.addClassName('input');
198
- cell.innerHTML = "<input type='text' value=''/>";
199
- var input = cell.select('input').first();
200
- input.value = value;
201
- input.setStyle({
202
- width: w + 'px',
203
- height: h + 'px'
204
- });
205
- input.observe('blur', Grid.close_cell);
206
- input.observe('keydown', Grid.keydown);
207
- input.observe('paste', Grid.paste);
208
- input.focus();
209
- input.select();
317
+ var value = val.value
318
+ var table = cell.up('table')
319
+
320
+ if (table.grid.list_name) {
321
+ if (value == 'on') {
322
+ cell.setAttribute('data-v', 'off')
323
+ } else {
324
+ cell.setAttribute('data-v', 'on')
325
+ }
326
+ Grid.closeCell(cell)
327
+ } else {
328
+ var w = cell.getWidth() - 5
329
+ var h = cell.getHeight() - 5
330
+ cell.addClassName('input')
331
+
332
+ // Try to find a form for the cell
333
+ var input
334
+ if (table.grid.helper && cell.tagName != 'TH') {
335
+ var pos = Grid.pos(cell)
336
+ input = table.grid.helper[pos]
337
+ if (input) {
338
+ input = Element.clone(input, true)
339
+ cell.update(input)
340
+ if (input.type == 'checkbox') {
341
+ if (value == input.value) {
342
+ input.checked = true
343
+ }
344
+ } else {
345
+ if (value && value.strip() != '') input.value = value
346
+ }
347
+ }
348
+ }
349
+
350
+ if (!input) {
351
+ // default input field
352
+ cell.update(Grid.default_input)
353
+ input = cell.childElements()[0]
354
+ input.value = value
355
+ }
356
+ input.setStyle({
357
+ width: w + 'px',
358
+ height: h + 'px'
359
+ })
360
+ input.observe('blur', Grid.closeCell)
361
+ input.observe('keydown', Grid.keydown)
362
+ input.observe('paste', Grid.paste)
363
+ input.focus()
364
+ input.select()
365
+ }
210
366
  }
211
367
 
212
368
  Grid.click = function(event) {
213
- var cell = event.findElement('td, th');
214
- var row = event.findElement('tr');
369
+ var cell = event.findElement('td, th')
370
+ var row = cell.up('tr')
371
+ var table = cell.up('table')
215
372
  if (row.hasClassName('action')) {
216
- Grid.action(event, cell, row, true);
373
+ Grid.action(event, cell, row, true)
217
374
  } else if (cell.hasClassName('action')) {
218
- Grid.action(event, cell, row, false);
375
+ Grid.action(event, cell, row, false)
376
+ } else if (cell.tagName == 'TH' && !table.grid.attr_name) {
377
+ // sort
378
+ Grid.sort(cell)
219
379
  } else {
220
- Grid.open_cell(cell);
380
+ Grid.openCell(cell)
221
381
  }
222
382
  }
223
383
 
224
- Grid.add_row = function(table, row) {
384
+ Grid.valueFromInput = function(input) {
385
+ var val = {}
386
+ if (input.tagName == 'SELECT') {
387
+ val.value = input.value
388
+ val.show = input.select('option[value="'+val.value+'"]').first().innerHTML
389
+ } else {
390
+ if (input.type == 'checkbox') {
391
+ val.value = input.checked ? input.value : (input.getAttribute('data-off') || cell.getAttribute('data-v'))
392
+ } else {
393
+ val.value = input.value
394
+ }
395
+ val.show = val.value
396
+ }
397
+ return val
398
+ }
399
+
400
+ Grid.getValue = function(cell) {
401
+ if (cell.hasClassName('input')) {
402
+ return Grid.valueFromInput(cell.childElements()[0])
403
+ }
404
+ var val = {}
405
+ val.show = cell.innerHTML
406
+ val.value = cell.getAttribute('data-v') || val.show
407
+ if (!cell.orig_value) cell.orig_value = val.value
408
+ return val
409
+ }
410
+
411
+ Grid.copy = function(cell) {
412
+ var table = cell.up('table')
413
+ var row = cell.up()
414
+ var pos = Grid.pos(cell)
415
+ if (cell.tagName == 'TH') {
416
+ row = row.nextSiblings()[0]
417
+ cell = row.childElements()[pos]
418
+ }
419
+ var val = Grid.getValue(cell)
420
+
421
+ table.grid.changes.push('start')
422
+ Grid.changed(cell, val, cell.prev_value, true)
423
+ cell.prev_value = val
424
+ var rows = row.nextSiblings()
425
+ var len = rows.length
426
+ for (var i = 0; i < len; i++) {
427
+ var c = rows[i].childElements()[pos]
428
+ if (!Grid.isReadOnly(c)) {
429
+ var prev = Grid.getValue(c)
430
+ if (prev.value != val.value) Grid.changed(c, val, prev)
431
+ }
432
+ }
433
+ table.grid.changes.push('end')
434
+ if (table.grid.attr_name) {
435
+ table.grid.input.value = Grid.serialize(table);
436
+ }
437
+ }
438
+
439
+ Grid.addRow = function(table, row) {
225
440
  // insert row below
226
- var new_row = '<tr>';
441
+ var row_str = '<tr>';
227
442
  var cells = row.childElements();
228
443
  for (var i = 0; i < cells.length -1; i++) {
229
- new_row = new_row + '<td></td>';
444
+ row_str = row_str + '<td></td>';
230
445
  }
231
- new_row = new_row + Grid.Buttons + '</tr>';
446
+ row_str = row_str + Grid.Buttons(table.grid) + '</tr>';
232
447
  row.insert({
233
- after: new_row
448
+ after: row_str
234
449
  });
235
450
  var new_row = row.nextSiblings()[0];
236
- // TODO: rewrite history (+ push event in history for undo)
451
+ if (table.grid.attr_name) {
452
+ // FIXME: rewrite history for undo
453
+ } else {
454
+ Grid.buildObj(table.grid, new_row)
455
+ }
456
+ return new_row
237
457
  }
238
458
 
239
- Grid.add_col = function(table, cell) {
459
+ Grid.addCol = function(table, cell) {
240
460
  var rows = table.childElements()[0].select('tr');
241
461
  var pos = Grid.pos(cell);
242
462
  for (var i = 0; i < rows.length; i++) {
@@ -259,7 +479,7 @@ Grid.add_col = function(table, cell) {
259
479
  }
260
480
  }
261
481
 
262
- Grid.del_col = function(table, cell) {
482
+ Grid.delCol = function(table, cell) {
263
483
  var rows = table.childElements()[0].select('tr');
264
484
  var pos = Grid.pos(cell);
265
485
  for (var i = 0; i < rows.length; i++) {
@@ -276,30 +496,49 @@ Grid.del_col = function(table, cell) {
276
496
  }
277
497
  }
278
498
 
499
+ Grid.delRow = function(grid, row) {
500
+ // remove current row
501
+ if (!grid.attr_name) {
502
+ // We must also clear the changes related to the removed row
503
+ Grid.clearChanges(grid.changes, row.id)
504
+ }
505
+ row.remove();
506
+ }
507
+
279
508
  Grid.action = function(event, cell, row, is_col) {
280
- var span = event.findElement('span');
281
- var table = event.findElement('table');
509
+ var span = event.findElement('span')
510
+ var table = event.findElement('table')
511
+ var grid = table.grid
282
512
  if (span.hasClassName('add')) {
283
513
  if (is_col) {
284
- Grid.add_col(table, cell);
514
+ Grid.addCol(table, cell);
285
515
  } else {
286
- Grid.add_row(table, row);
287
- Grid.open_cell(new_row.childElements()[0]);
516
+ var new_row = Grid.addRow(table, row);
517
+ Grid.openCell(new_row.childElements()[0]);
288
518
  }
289
519
  } else if (span.hasClassName('del')) {
290
520
  if (is_col) {
291
- Grid.del_col(table, cell);
521
+ Grid.delCol(table, cell);
292
522
  } else {
293
- // remove current row
294
- row.remove();
523
+ if (event.altKey) {
524
+ // remove current row and all unchanged below
525
+ var rows = table.select('tr')
526
+ var row_i = Grid.pos(row)
527
+ for(var i = rows.length - 1; i >= row_i; --i) {
528
+ var arow = rows[i]
529
+ if (!arow.hasClassName('changed')) Grid.delRow(grid, arow)
530
+ }
531
+ } else {
532
+ Grid.delRow(grid, row)
533
+ }
295
534
  }
296
535
  } else if (span.hasClassName('copy')) {
297
536
  var data = Grid.serialize(table, 'tab');
298
537
  var td = span.up();
299
538
  td.insert({
300
- top: "<textarea id='grid_copy_" + table.grid.id + "'></textarea>"
539
+ top: "<textarea id='grid_copy_" + grid.id + "'></textarea>"
301
540
  });
302
- var input = $('grid_copy_'+table.grid.id);
541
+ var input = $('grid_copy_'+grid.id);
303
542
  input.value = data;
304
543
  Element.observe($(input), 'blur', function(event) {
305
544
  event.element().remove();
@@ -307,16 +546,25 @@ Grid.action = function(event, cell, row, is_col) {
307
546
  input.focus();
308
547
  input.select();
309
548
  }
310
- table.grid.input.value = Grid.serialize(table);
549
+ if (grid.attr_name) {
550
+ grid.input.value = Grid.serialize(table);
551
+ }
311
552
  }
312
553
 
313
554
  // map grid position to attribute and reverse.
314
555
  Grid.makeAttrPos = function(table) {
315
- var heads = table.childElements()[0].select('th');
556
+ var heads = table.select('th');
316
557
  var attr = {};
317
558
  var pos = {};
318
- table.grid.attr = attr;
319
- table.grid.pos = pos;
559
+ var helper = {}
560
+ var defaults = {}
561
+ var helpers
562
+ table.grid.attr = attr
563
+ table.grid.pos = pos
564
+ if (table.grid.helper_id) helpers = $(table.grid.helper_id)
565
+ if (helpers) {
566
+ table.grid.helper = helper;
567
+ }
320
568
  if (table.grid.attr_name) {
321
569
  for (var i = 0; i < heads.length; i++) {
322
570
  attr[i] = i;
@@ -327,8 +575,20 @@ Grid.makeAttrPos = function(table) {
327
575
  var attr_name = heads[i].getAttribute('data-a');
328
576
  attr[i] = attr_name;
329
577
  pos[attr_name] = i;
578
+ if (helpers) {
579
+ helper[i] = helpers.select('*[name="'+attr_name+'"]').first()
580
+ }
581
+ }
582
+ // get default values
583
+ if (helpers) {
584
+ helpers.select('input,textarea,select').each(function(e) {
585
+ if (e.getAttribute('data-d') == 'true') {
586
+ defaults[e.name] = Grid.valueFromInput(e)
587
+ }
588
+ })
330
589
  }
331
590
  }
591
+ table.grid.defaults = $H(defaults)
332
592
  }
333
593
 
334
594
  // only used with single attr table
@@ -341,9 +601,9 @@ Grid.serialize = function(table, format) {
341
601
  for (var j = 0; j < cells.length - 1; j++) {
342
602
  var cell = cells[j];
343
603
  if (cell.hasClassName('input')) {
344
- row_data.push(cell.select('input').first().value);
604
+ row_data.push(cell.childElements()[0].value)
345
605
  } else {
346
- row_data.push(cells[j].innerHTML);
606
+ row_data.push(cell.innerHTML)
347
607
  }
348
608
  }
349
609
  data.push(row_data);
@@ -368,49 +628,118 @@ Grid.serialize = function(table, format) {
368
628
  }
369
629
  }
370
630
 
371
- Grid.Buttons = "<td class='action'><span class='add'>&nbsp;</span> <span class='del'>&nbsp;</span></td>";
372
- Grid.ColButtons = Grid.Buttons;
631
+ Grid.Buttons = function(grid) {
632
+ var btns = "<td class='action'>"
633
+ if (grid.add) btns = btns + "<span class='add'>&nbsp;</span>"
634
+ if (grid.remove) btns = btns + "<span class='del'>&nbsp;</span>"
635
+ btns = btns + "</td>"
636
+ return btns
637
+ }
638
+
639
+ Grid.ColButtons = "<td><span class='del'>&nbsp;</span> <span class='add'>&nbsp;</span></td>"
373
640
 
374
641
  // only used with single attr table
375
642
  Grid.addButtons = function(table) {
376
- var data = [];
377
- var tbody = table.childElements()[0];
378
- var rows = tbody.select('tr');
643
+ var grid = table.grid
644
+ var attr_table = grid.attr_name
645
+ var data = []
646
+ var tbody = table.childElements()[0]
647
+ var rows = tbody.select('tr')
379
648
 
380
- var col_action = "<tr class='action'><td><span class='add'>&nbsp;</span></td>";
381
- var cells_length = rows[0].select('th').length;
382
- for (var i = 1; i < cells_length; i++) {
383
- col_action = col_action + Grid.ColButtons;
649
+ if (attr_table) {
650
+ var col_action = "<tr class='action'><td><span class='add'>&nbsp;</span></td>";
651
+ var cells_length = rows[0].select('th').length;
652
+ for (var i = 1; i < cells_length; i++) {
653
+ col_action = col_action + Grid.ColButtons;
654
+ }
655
+ col_action = col_action + "<td class='action'><span class='copy'>&nbsp;</span></td></tr>";
384
656
  }
385
- col_action = col_action + "<td class='action'><span class='copy'>&nbsp;</span></td></tr>";
386
657
 
387
658
  for (var i = 0; i < rows.length; i++) {
388
- var buttons;
659
+ var buttons
389
660
  if (i == 0) {
390
- buttons = "<td class='action'><span class='add'>&nbsp;</span></td>";
661
+ if (grid.add) {
662
+ buttons = "<td class='action'><span class='add'>&nbsp;</span></td>"
663
+ } else {
664
+ buttons = "<td class='action'></td>"
665
+ }
391
666
  } else {
392
- buttons = Grid.Buttons;
667
+ buttons = Grid.Buttons(grid)
393
668
  }
394
669
  rows[i].insert({
395
670
  bottom: buttons
396
- });
671
+ })
397
672
  }
398
673
  tbody.insert({
399
674
  top: col_action
400
- });
401
- return data;
675
+ })
676
+ return data
677
+ }
678
+
679
+ Grid.onFailure = function(grid, id, errors) {
680
+ var cells = $(id).select('td')
681
+ errors.each(function(pair) {
682
+ var pos = grid.pos[pair.key] || -1
683
+ var cell = cells[pos]
684
+ if (cell) {
685
+ Grid.setError(cell, pair.value)
686
+ } else {
687
+ // generic error
688
+ Grid.setError($(id), pair.key + ': ' + pair.value)
689
+ }
690
+ })
691
+ }
692
+
693
+ Grid.setError = function(e, msg) {
694
+ if (!Grid.msg) {
695
+ Grid.msg = $('grid_msg_')
696
+ if (!Grid.msg) {
697
+ $(document.body).insert({
698
+ bottom: "<div id='grid_msg_' style='display:none' class='grid_msg'></div>"
699
+ })
700
+ Grid.msg = $('grid_msg_')
701
+ }
702
+ Grid.msg.absolutize()
703
+ }
704
+ e.observe('mouseover', function() {
705
+ Grid.msg.innerHTML = msg
706
+ Element.clonePosition(Grid.msg, e, {
707
+ setWidth:false,
708
+ setHeight:false,
709
+ offsetTop:3,
710
+ offsetLeft:e.getWidth() - 3,
711
+ })
712
+ Grid.msg.show()
713
+ })
714
+ e.observe('mouseout', function() {
715
+ Grid.msg.hide()
716
+ })
717
+ e.addClassName('error')
402
718
  }
403
719
 
404
- Grid.make = function(table) {
720
+ Grid.make = function(table, opts) {
721
+ opts = opts || {}
722
+ table = $(table)
405
723
  if (table.grid) return;
406
724
  Grid.grid_c++;
407
725
  Grid.grids[Grid.grid_c] = table;
408
726
  table.grid = {
409
727
  changes: [],
410
728
  id: Grid.grid_c,
729
+ helper_id: table.getAttribute('data-helper'),
730
+ fdate: table.getAttribute('data-fdate'),
731
+ counter: 0, // Used to create dom_ids for new objects
732
+ onSuccess: opts.onSuccess,
733
+ onFailure: opts.onFailure || Grid.onFailure,
734
+ onStart: opts.onStart || Grid.onStart,
735
+ add: opts.add || opts.add == undefined,
736
+ remove: opts.remove || opts.remove == undefined,
737
+ keydown: opts.keydown,
411
738
  };
739
+
412
740
  // Detect type.
413
741
  table.grid.attr_name = table.getAttribute('data-a');
742
+ table.grid.list_name = table.getAttribute('data-l');
414
743
 
415
744
  var empty = false;
416
745
  if (table.grid.attr_name && table.select('th').length == 0) {
@@ -419,18 +748,25 @@ Grid.make = function(table) {
419
748
  table.innerHTML = "<tr><th>" + msg + "</th></tr><tr><td></td></tr>";
420
749
  }
421
750
 
422
- Grid.makeAttrPos(table);
751
+ Grid.makeAttrPos(table)
752
+ Grid.addButtons(table)
753
+
423
754
 
424
755
  if (table.grid.attr_name) {
425
756
  // If we have an attr_name, rows and columns are
426
757
  // serialized as json in a single field.
427
- Grid.addButtons(table);
428
758
  table.insert({
429
759
  after: "<input type='hidden' id='grid_a_" + Grid.grid_c + "' name='" + table.grid.attr_name + "'/>"
430
760
  });
431
761
  table.grid.input = $("grid_a_" + Grid.grid_c);
432
762
  if (!empty) table.grid.input.value = Grid.serialize(table);
433
763
  } else {
764
+ var rows = table.select('tr')
765
+ for (var i = 1; i < rows.length; i++) {
766
+ if (!rows[i].id) {
767
+ Grid.buildObj(table.grid, rows[i])
768
+ }
769
+ }
434
770
  // Otherwise each row is a new object and each column
435
771
  // corresponds to a different attribute (defined in the
436
772
  // 'th' of the table).
@@ -439,58 +775,170 @@ Grid.make = function(table) {
439
775
  });
440
776
  }
441
777
 
442
-
443
778
  table.observe('click', Grid.click);
444
779
  }
445
780
 
781
+ // Default onStart handler
782
+ Grid.onStart = function(operations) {
783
+ if (operations.post) {
784
+ return confirm('Create '+operations.post+' nodes ?')
785
+ }
786
+ return true
787
+ }
788
+
789
+ Grid.clearChanges = function(list, id) {
790
+ for (var i = list.length - 1; i >= 0; i--) {
791
+ while (list[i] && list[i].id == id) {
792
+ list.splice(i, 1)
793
+ }
794
+ }
795
+ }
796
+
797
+ Grid.isChanged = function(elem) {
798
+ var table = $(elem)
799
+ var grid = table.grid
800
+ // buildObj adds a row per new object on load
801
+ return grid.changes.length > (table.attr_name ? 0 : table.select('tr').length - 1)
802
+ }
803
+
804
+
446
805
  Grid.save = function(grid_id) {
447
- var table = Grid.grids[grid_id];
448
- var data = {};
449
- data[grid_id] = Grid.compact(table.grid.changes);
450
- // PUT changes
451
- // ... We would trigger Ajax call here
452
- new Ajax.Request("/echo/json/", {
453
- parameters: {
454
- json:Object.toJSON(data)
455
- },
456
- onSuccess: function(transport) {
457
- Grid.notify(transport.responseJSON);
458
- },
459
- method: 'post'
460
- });
461
- // MOCK by receiving sent data
462
- //Grid.notify(data);
806
+ // do not run on GUI thread
807
+ setTimeout(function() {
808
+ var table = Grid.grids[grid_id]
809
+ var grid = table.grid
810
+ var data = Grid.compact(grid.changes)
811
+ if (grid.list_name) {
812
+ data = Grid.dataForList(grid, data)
813
+ }
814
+ var todo_count = data.keys().length
815
+ var done_count = 0
816
+ if (grid.onStart) {
817
+ var operations = {}
818
+ data.each(function(pair) {
819
+ if (pair.value._new) {
820
+ operations.post = (operations.post || 0) + 1
821
+ } else {
822
+ operations.put = (operations.put || 0) + 1
823
+ }
824
+ })
825
+ if (!grid.onStart(operations)) return
826
+ }
827
+ data.each(function(pair) {
828
+ var id = pair.key
829
+ var changes = pair.value
830
+ var attrs = {zjs:true, "opts[format]":grid.fdate}
831
+ $H(changes).each(function(pair) {
832
+ if (pair.key != '_new') {
833
+ attrs['node['+pair.key+']'] = pair.value
834
+ }
835
+ })
836
+ if (changes._new) {
837
+ new Ajax.Request('/nodes', {
838
+ parameters: attrs,
839
+ onSuccess: function(transport) {
840
+ done_count++
841
+ var reply = transport.responseText.evalJSON()
842
+ // Change row id: it is no longer a new item
843
+ var old_id = id
844
+ $(id).id = 'id_' + reply.id
845
+ id = 'id_' + reply.id
846
+ var attrs = {}
847
+ attrs[id] = reply
848
+ Grid.notify(table, attrs)
849
+ Grid.clearChanges(grid.changes, old_id)
850
+ if (grid.onSuccess) {
851
+ grid.onSuccess(grid, id, 'post', done_count, todo_count)
852
+ }
853
+ },
854
+
855
+ onFailure: function(transport) {
856
+ done_count++
857
+ var errors = {}
858
+ transport.responseText.evalJSON().each(function(e) {
859
+ errors[e[0]] = e[1]
860
+ })
861
+ // Change row id: it is no longer a new item
862
+ grid.onFailure(grid, id, $H(errors))
863
+ },
864
+ method: 'post'
865
+ });
866
+ } else {
867
+ new Ajax.Request('/nodes/' + id.replace('id_',''), {
868
+ parameters: attrs,
869
+ onSuccess: function(transport) {
870
+ done_count++
871
+ var attrs = {}
872
+ attrs[id] = transport.responseText.evalJSON()
873
+ Grid.notify(table, attrs)
874
+ Grid.clearChanges(grid.changes, id)
875
+ if (grid.onSuccess) {
876
+ grid.onSuccess(id, 'put', done_count, todo_count)
877
+ }
878
+ },
879
+ method: 'put'
880
+ });
881
+ }
882
+ })
883
+ }, 100);
463
884
  }
464
885
 
465
- Grid.undo = function(grid_id) {
466
- var table = Grid.grids[grid_id];
467
- var change = table.grid.changes.pop();
468
- // TODO: could be optimized
469
- var state = Grid.compact(table.grid.changes)[change.id] || {};
886
+ Grid.undo = function(grid_id, skip_undone) {
887
+ var table = Grid.grids[grid_id]
888
+ var grid = table.grid
889
+ var changes = grid.changes
890
+ var last = changes.last()
891
+ if (!last || last._new) return
892
+ var group = false
893
+ if (last == 'end') {
894
+ group = true
895
+ changes.pop()
896
+ }
897
+ var change = changes.pop()
898
+ var old = change._old
470
899
  for (attr in change) {
471
- if (attr == 'id') continue;
472
- var cell = $(change.id).childElements()[table.grid.pos[attr]];
473
- var value = state[attr] || cell.orig_value;
474
- cell.innerHTML = value;
475
- cell.prev_value = value;
900
+ if (attr == 'id' || attr == '_old') continue
901
+ var cell = $(change.id).childElements()[grid.pos[attr]]
902
+ var val = old
903
+ var value = old.value
904
+ cell.innerHTML = val.show || ''
905
+ cell.prev_value = val
476
906
  if (value == cell.orig_value) {
477
- cell.removeClassName('changed');
478
- var row = cell.up();
479
- if (row.select('.changed').length == 0) row.removeClassName('changed');
480
- } else {[cell, cell.up()].invoke('addClassName', 'changed');
907
+ cell.removeClassName('changed')
908
+ var row = cell.up()
909
+ if (row.select('.changed').length == 0) row.removeClassName('changed')
910
+ } else {
911
+ [cell, cell.up()].invoke('addClassName', 'changed')
912
+ }
913
+ cell.addClassName('undone')
914
+ if (grid.list_name) {
915
+ cell.setAttribute('data-v', value)
916
+ if (value == 'on') {
917
+ cell.addClassName('on')
918
+ } else {
919
+ cell.removeClassName('on')
920
+ }
921
+ }
922
+ }
923
+ if (group) {
924
+ while (changes.last() != 'start') {
925
+ Grid.undo(grid_id, true)
481
926
  }
482
- cell.addClassName('undone');
927
+ changes.pop()
928
+ }
929
+
930
+ if (!skip_undone) {
931
+ setTimeout(function() {
932
+ table.select('.undone').invoke('removeClassName', 'undone')
933
+ }, 1000)
483
934
  }
484
- new PeriodicalExecuter(function(pe) {
485
- table.select('.undone').invoke('removeClassName', 'undone');
486
- pe.stop();
487
- }, 1);
488
935
  }
489
936
 
490
937
  Grid.compact = function(list) {
491
938
  var res = {};
492
939
  for (var i = list.length - 1; i >= 0; i--) {
493
940
  var changes = list[i];
941
+ if (typeof(changes) == 'string') continue
494
942
  var obj = res[changes.id];
495
943
  if (!obj) {
496
944
  obj = {};
@@ -498,50 +946,203 @@ Grid.compact = function(list) {
498
946
  }
499
947
 
500
948
  for (var key in changes) {
501
- if (key != 'id' && !obj[key]) {
949
+ if (key != 'id' && key != '_old' && !obj[key]) {
502
950
  // only take latest change
503
- obj[key] = changes[key];
951
+ obj[key] = changes[key].value;
504
952
  }
505
953
  }
506
954
  }
507
- return res;
955
+ return $H(res);
508
956
  }
509
957
 
510
- Grid.notify = function(data) {
511
- for (var i in data) {
512
- var table = Grid.grids[parseInt(i)];
513
- var rows = table.childElements()[0].select('tr');
514
- var changes = data[i];
515
- var pos = table.grid.pos;
516
- for (var obj_id in changes) {
517
- var row = $(obj_id);
518
- if (!row) {
519
- // attr table
520
- row = rows[parseInt(obj_id)+1];
521
- }
522
- var cells = row.childElements();
523
- var change = changes[obj_id];
524
- for (var attr in change) {
525
- if (attr == 'id') continue;
526
- var cell;
527
- cell = cells[table.grid.pos[attr]];
528
- cell.removeClassName('changed');
529
- cell.innerHTML = change[attr];
530
- cell.orig_value = change[attr];
531
- cell.prev_value = undefined;
532
- cell.addClassName('saved');
958
+ Grid.test = function() {
959
+ var grid = $('grid').grid
960
+ var data = Grid.compact(grid.changes)
961
+ return Grid.dataForList(grid, data)
962
+ }
963
+
964
+ // Build the changes array when we have a list. This function
965
+ // detects which rows have changes and builds the full list of
966
+ // ids in the format some_relation_ids:"123,345,888,432". Other fields
967
+ // are kept as is.
968
+ Grid.dataForList = function(grid, data) {
969
+ var res = {}
970
+ var list_name = grid.list_name
971
+ data.each(function(pair) {
972
+ var obj = {}
973
+ var base = pair.value
974
+ res[pair.key] = obj
975
+ for (var key in base) {
976
+ if (parseInt(key) + '' == key) {
977
+ // number key = there is a change in the list
978
+ if (!obj[list_name]) {
979
+ // build full list
980
+ var list = []
981
+ var row = $(pair.key)
982
+ var cells = row.childElements()
983
+ for (var i = 0; i < cells.length - 1; i++) {
984
+ var cell = cells[i]
985
+ var attr = grid.attr[i]
986
+ if (attr && (parseInt(attr) + '' == attr)) {
987
+ // number attr key
988
+ if (cell.getAttribute('data-v') == 'on') list.push(attr)
989
+ }
990
+ }
991
+ obj[list_name] = list.join(',')
992
+ }
993
+ } else {
994
+ // keep other attributes unchanged
995
+ obj[key] = base[key]
533
996
  }
534
- if (row.select('.changed').length == 0) {
997
+ }
998
+ })
999
+ return $H(res)
1000
+ }
535
1001
 
536
- row.removeClassName('changed');
1002
+ Grid.notify = function(table, changes) {
1003
+ var rows = table.childElements()[0].select('tr')
1004
+ var grid = table.grid
1005
+ var pos = grid.pos
1006
+ for (var obj_id in changes) {
1007
+ var row
1008
+ if (grid.attr_name) {
1009
+ // attr table
1010
+ row = rows[parseInt(obj_id)+1]
1011
+ } else {
1012
+ row = $(obj_id)
1013
+ }
1014
+ var cells = row.childElements()
1015
+ var change = changes[obj_id]
1016
+ for (var attr in change) {
1017
+ if (attr == 'id') continue
1018
+ if (attr == grid.list_name) {
1019
+ var list_on = change[attr].split(/,/)
1020
+ var cells = row.childElements()
1021
+ for (var i = 0; i < cells.length - 1; i++) {
1022
+ var attr = grid.attr[i]
1023
+ var cell = cells[i]
1024
+ if (parseInt(attr) + '' == attr) {
1025
+ if (list_on.indexOf(attr) >= 0) {
1026
+ cell.orig_value = 'on'
1027
+ cell.setAttribute('data-v', 'on')
1028
+ } else {
1029
+ cell.orig_value = 'off'
1030
+ cell.setAttribute('data-v', 'off')
1031
+ }
1032
+ cell.prev_value = undefined
1033
+ cell.removeClassName('error')
1034
+ cell.removeClassName('changed')
1035
+ cell.addClassName('saved')
1036
+ }
1037
+ }
1038
+ } else {
1039
+ var cell
1040
+ var i = pos[attr]
1041
+ if (i == undefined) continue
1042
+ cell = cells[i]
1043
+ cell.removeClassName('changed')
1044
+ cell.removeClassName('error')
1045
+ if (cell.getAttribute('data-v') != change[attr]) {
1046
+ cell.innerHTML = change[attr]
1047
+ }
1048
+ cell.orig_value = change[attr]
1049
+ cell.prev_value = undefined
1050
+ cell.addClassName('saved')
537
1051
  }
538
1052
  }
1053
+ row.removeClassName('new')
1054
+ row.removeClassName('error')
1055
+ if (row.select('.changed').length == 0) {
1056
+ row.removeClassName('changed')
1057
+ }
539
1058
  }
540
1059
  // later
541
- new PeriodicalExecuter(function(pe) {
542
- table.select('.saved').invoke('removeClassName', 'saved');
543
- pe.stop();
544
- }, 1);
545
- // maybe this is not good
546
- table.grid.changes = []; // clear
1060
+ setTimeout(function() {
1061
+ table.select('.saved').invoke('removeClassName', 'saved')
1062
+ }, 1000)
547
1063
  }
1064
+
1065
+ Grid.simulateClick = function(l) {
1066
+ if (document.createEvent) {
1067
+ var e = document.createEvent('MouseEvents')
1068
+ e.initMouseEvent('click', true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, l)
1069
+ l.dispatchEvent(e)
1070
+ } else {
1071
+ var e = Object.extend(document.createEventObject())
1072
+ l.fireEvent('onclick', e)
1073
+ }
1074
+ }
1075
+
1076
+ Grid.sort = function(cell) {
1077
+ var table = cell.up('table')
1078
+ var desc = false
1079
+ if (cell.hasClassName('asc')) {
1080
+ desc = true
1081
+ cell.removeClassName('asc')
1082
+ cell.addClassName('desc')
1083
+ } else {
1084
+ table.select('.asc, .desc').each(function(e) { e.removeClassName('asc').removeClassName('desc') })
1085
+ cell.addClassName('asc')
1086
+ }
1087
+ var body = table.childElements()[0]
1088
+ var rows = body.select('tr')
1089
+ rows.splice(0,1)
1090
+ var col_i = Grid.pos(cell)
1091
+
1092
+ rows.sort(function(a, b) {
1093
+ var atxt = a.childElements()[col_i].innerHTML.stripTags().toLowerCase()
1094
+ var btxt = b.childElements()[col_i].innerHTML.stripTags().toLowerCase()
1095
+ return atxt.localeCompare(btxt) * (desc ? -1 : 1)
1096
+ }).each(Element.prototype.appendChild, body)
1097
+ }
1098
+
1099
+ /////////// Tags
1100
+ Tags = {}
1101
+
1102
+ Tags.click = function(event) {
1103
+ var e = event.element()
1104
+ var value = e.getAttribute('data-v') || e.innerHTML
1105
+ var tags = e.tags
1106
+ var list = tags.list
1107
+ for (var i = list.length - 1; i >= 0; i--) {
1108
+ while (list[i] && list[i] == value) {
1109
+ list.splice(i, 1)
1110
+ }
1111
+ }
1112
+ tags.onChange(list, e)
1113
+ }
1114
+
1115
+ Tags.add = function(event) {
1116
+ var e = event.element()
1117
+ var value = e.value
1118
+ var tags = e.tags
1119
+ var list = tags.list
1120
+ for (var i = list.length - 1; i >= 0; i--) {
1121
+ while (list[i] && list[i] == value) {
1122
+ list.splice(i, 1)
1123
+ }
1124
+ }
1125
+ list.push(value)
1126
+ tags.onChange(list)
1127
+ }
1128
+
1129
+ Tags.make = function(elem, opts) {
1130
+ var tags = {}
1131
+ tags.onChange = opts.onChange
1132
+ elem.tags = tags
1133
+ var list = []
1134
+ tags.list = list
1135
+ elem.childElements().each(function(e) {
1136
+ var input = e.select('input,select').first()
1137
+ if (input) {
1138
+ input.tags = tags
1139
+ input.observe('change', Tags.add)
1140
+ } else {
1141
+ e.tags = tags
1142
+ list.push(e.getAttribute('data-v') || e.innerHTML)
1143
+ e.observe('click', Tags.click)
1144
+ }
1145
+ })
1146
+ }
1147
+
1148
+