zena 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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
+