zena 1.2.2 → 1.2.3

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 (143) hide show
  1. data/History.txt +25 -0
  2. data/app/controllers/documents_controller.rb +3 -25
  3. data/app/controllers/nodes_controller.rb +34 -24
  4. data/app/controllers/user_sessions_controller.rb +5 -4
  5. data/app/controllers/versions_controller.rb +44 -17
  6. data/app/models/acl.rb +2 -7
  7. data/app/models/group.rb +6 -2
  8. data/app/models/link.rb +14 -0
  9. data/app/models/node.rb +2 -2
  10. data/app/models/site.rb +13 -4
  11. data/app/models/text_document.rb +1 -1
  12. data/app/models/user.rb +11 -2
  13. data/app/models/virtual_class.rb +1 -1
  14. data/app/views/groups/_form.rhtml +6 -6
  15. data/app/views/nodes/render_error.rhtml +15 -0
  16. data/app/views/templates/document_create_tabs/_file.rhtml +1 -1
  17. data/app/views/templates/document_create_tabs/_import.rhtml +1 -1
  18. data/app/views/templates/document_create_tabs/_template.rhtml +1 -1
  19. data/app/views/templates/document_create_tabs/_text_document.rhtml +1 -1
  20. data/app/views/templates/edit_tabs/_title.rhtml +1 -1
  21. data/app/views/zafu/default/Node-admin.zafu +1 -1
  22. data/bricks/acls/zena/test/integration/acl_integration_test.rb +2 -2
  23. data/bricks/acls/zena/test/unit/acl_test.rb +2 -1
  24. data/bricks/fs_skin/zena/migrate/20110702010330_add_fs_skin_to_idx_templates.rb +1 -0
  25. data/bricks/fs_skin/zena/skins/blog/img/style.css +4 -4
  26. data/bricks/grid/lib/bricks/grid.rb +9 -3
  27. data/bricks/passenger/zena/deploy.rb +2 -1
  28. data/bricks/pdf/lib/bricks/pdf.rb +1 -1
  29. data/bricks/tags/zena/test/zafu/tags.yml +5 -1
  30. data/bricks/zena/zena/migrate/20120904071601_change_link_status_to_float.rb +13 -0
  31. data/config/bricks.yml +10 -10
  32. data/config/deploy.rb +1 -5
  33. data/config/gems.yml +2 -2
  34. data/db/init/base/skins/default/Node.zafu +7 -3
  35. data/db/init/base/skins/default/notes.zafu +3 -1
  36. data/lib/zafu/all.rb +0 -9
  37. data/lib/zafu/compiler.rb +0 -4
  38. data/lib/zafu/controller_methods.rb +0 -2
  39. data/lib/zafu/handler.rb +0 -5
  40. data/lib/zafu/markup.rb +4 -6
  41. data/lib/zafu/ordered_hash.rb +3 -2
  42. data/lib/zafu/parsing_rules.rb +1 -3
  43. data/lib/zafu/process/ajax.rb +4 -2
  44. data/lib/zafu/process/context.rb +34 -4
  45. data/lib/zafu/process/forms.rb +2 -2
  46. data/lib/zafu/process/ruby_less_processing.rb +5 -10
  47. data/lib/zafu/template.rb +0 -2
  48. data/lib/zafu/test_helper.rb +0 -2
  49. data/lib/zafu/view_methods.rb +0 -1
  50. data/lib/zafu.rb +1 -1
  51. data/lib/zena/acts/secure_node.rb +5 -4
  52. data/lib/zena/console.rb +19 -17
  53. data/lib/zena/core_ext/string.rb +3 -2
  54. data/lib/zena/deploy/app_init.rhtml +6 -1
  55. data/lib/zena/deploy/httpd.rhtml +16 -13
  56. data/lib/zena/deploy/stats.vhost.rhtml +1 -1
  57. data/lib/zena/deploy/vhost.rhtml +31 -11
  58. data/lib/zena/deploy/vhost_ssl_redir.rhtml +12 -0
  59. data/lib/zena/deploy/vhost_www.rhtml +1 -1
  60. data/lib/zena/deploy.rb +55 -11
  61. data/lib/zena/info.rb +1 -1
  62. data/lib/zena/parser/zazen_rules.rb +18 -9
  63. data/lib/zena/routes.rb +1 -3
  64. data/lib/zena/site_worker.rb +8 -1
  65. data/lib/zena/use/ajax.rb +29 -3
  66. data/lib/zena/use/ancestry.rb +2 -1
  67. data/lib/zena/use/authlogic.rb +12 -18
  68. data/lib/zena/use/context.rb +1 -1
  69. data/lib/zena/use/dates.rb +28 -18
  70. data/lib/zena/use/display.rb +49 -7
  71. data/lib/zena/use/forms.rb +51 -18
  72. data/lib/zena/use/html_tags.rb +6 -6
  73. data/lib/zena/use/i18n.rb +13 -4
  74. data/lib/zena/use/image_builder.rb +2 -0
  75. data/lib/zena/use/query_builder.rb +39 -14
  76. data/lib/zena/use/query_link.rb +57 -0
  77. data/lib/zena/use/query_node.rb +68 -32
  78. data/lib/zena/use/relations.rb +25 -15
  79. data/lib/zena/use/rendering.rb +66 -15
  80. data/lib/zena/use/upload.rb +34 -5
  81. data/lib/zena/use/urls.rb +28 -25
  82. data/lib/zena/use/version_hash.rb +14 -2
  83. data/lib/zena/use/zafu_safe_definitions.rb +72 -3
  84. data/lib/zena/use/zazen.rb +16 -4
  85. data/lib/zena.rb +1 -0
  86. data/public/javascripts/grid.js +213 -64
  87. data/public/javascripts/raphael.js +10 -0
  88. data/public/javascripts/zena.js +146 -22
  89. data/public/stylesheets/reset.css +12 -12
  90. data/public/stylesheets/zena.css +1 -1
  91. data/test/custom_queries/complex.host.yml +19 -0
  92. data/test/fixtures/files/TestNode.zafu +40 -4
  93. data/test/functional/nodes_controller_test.rb +84 -39
  94. data/test/functional/versions_controller_test.rb +2 -2
  95. data/test/integration/navigation_test.rb +61 -35
  96. data/test/integration/query_node/basic.yml +7 -7
  97. data/test/integration/query_node/comments.yml +1 -1
  98. data/test/integration/query_node/complex.yml +3 -3
  99. data/test/integration/query_node/filters.yml +32 -8
  100. data/test/integration/query_node/idx_key_value.yml +10 -10
  101. data/test/integration/query_node/idx_scope.yml +7 -7
  102. data/test/integration/query_node/relations.yml +4 -4
  103. data/test/integration/zafu_compiler/ajax.yml +19 -11
  104. data/test/integration/zafu_compiler/apphelper.yml +1 -1
  105. data/test/integration/zafu_compiler/asset.yml +2 -2
  106. data/test/integration/zafu_compiler/comments.yml +1 -1
  107. data/test/integration/zafu_compiler/dates.yml +1 -1
  108. data/test/integration/zafu_compiler/display.yml +49 -21
  109. data/test/integration/zafu_compiler/eval.yml +4 -4
  110. data/test/integration/zafu_compiler/forms.yml +25 -11
  111. data/test/integration/zafu_compiler/i18n.yml +5 -0
  112. data/test/integration/zafu_compiler/meta.yml +3 -3
  113. data/test/integration/zafu_compiler/query.yml +27 -9
  114. data/test/integration/zafu_compiler/relations.yml +9 -9
  115. data/test/integration/zafu_compiler/roles.yml +6 -6
  116. data/test/integration/zafu_compiler/rubyless.yml +7 -2
  117. data/test/integration/zafu_compiler/safe_definitions.yml +33 -4
  118. data/test/integration/zafu_compiler/security.yml +46 -1
  119. data/test/integration/zafu_compiler/urls.yml +28 -13
  120. data/test/integration/zafu_compiler/user.yml +12 -7
  121. data/test/integration/zafu_compiler/zafu_attributes.yml +1 -1
  122. data/test/integration/zafu_compiler/zazen.yml +5 -5
  123. data/test/integration/zafu_compiler_test.rb +18 -0
  124. data/test/selenium/Filter/filter3.rsel +20 -0
  125. data/test/selenium/Filter/filter4.rsel +20 -0
  126. data/test/sites/zena/versions.yml +2 -0
  127. data/test/unit/exif_data_test.rb +6 -1
  128. data/test/unit/group_test.rb +18 -3
  129. data/test/unit/node_test.rb +0 -7
  130. data/test/unit/project_test.rb +4 -0
  131. data/test/unit/relation_proxy_test.rb +2 -2
  132. data/test/unit/remote_test.rb +0 -9
  133. data/test/unit/role_test.rb +1 -1
  134. data/test/unit/string_hash_test.rb +1 -1
  135. data/test/unit/text_document_test.rb +13 -13
  136. data/test/unit/zena/use/html_tags_test.rb +6 -6
  137. data/test/unit/zena/use/rendering_test.rb +20 -10
  138. data/test/unit/zena/use/urls_test.rb +21 -18
  139. data/test/unit/zena/use/zafu_template_test.rb +0 -5
  140. data/test/unit/zena/use/zazen_test.rb +25 -25
  141. data/zena.gemspec +63 -57
  142. metadata +136 -130
  143. data/test/functional/nodes_controller_commit_test.rb +0 -67
@@ -1,10 +1,66 @@
1
1
  module Zena
2
2
  module Use
3
3
  module ZafuSafeDefinitions
4
+ # This is a dummy class to declare safe parameters on params.
4
5
  class ParamsDictionary
5
6
  include RubyLess
6
7
  safe_method ['[]', Symbol] => {:class => String, :nil => true}
7
8
  end
9
+
10
+ # This class is used to return Array params ['foo', 'bar'] as ['foo', 'bar'], not 'foobar'.
11
+ # It also splits String params 'foo, bar' as ['foo', 'bar'].
12
+ class AParamsDictionary
13
+ def initialize(params)
14
+ @aparams = {}
15
+ params.each do |k, v|
16
+ @aparams[k.to_sym] = transform(v)
17
+ end
18
+
19
+ def [](key)
20
+ @aparams[key.to_sym] || []
21
+ end
22
+ end
23
+
24
+ include RubyLess
25
+ safe_method ['[]', Symbol] => {:class => [String], :nil => false}
26
+
27
+ private
28
+ def transform(v)
29
+ if v.kind_of?(Array)
30
+ v.map {|k| k.to_s}.select{|e| !e.blank?}
31
+ elsif v.kind_of?(String)
32
+ v.split(',').map(&:strip).select{|e| !e.blank?}
33
+ else
34
+ []
35
+ end
36
+ end
37
+ end
38
+
39
+ # This class is used to access nested Hash params {:key => 'foo'}.
40
+ class HParamsDictionary
41
+ def initialize(params)
42
+ @params = {}
43
+ params.each do |k, v|
44
+ @params[k.to_sym] = transform(v)
45
+ end
46
+ end
47
+
48
+ def [](key)
49
+ @params[key.to_sym] || {}
50
+ end
51
+
52
+ include RubyLess
53
+ safe_method ['[]', Symbol] => {:class => ParamsDictionary, :nil => false}
54
+
55
+ private
56
+ def transform(v)
57
+ if v.kind_of?(Hash)
58
+ v
59
+ else
60
+ {}
61
+ end
62
+ end
63
+ end
8
64
 
9
65
  module ViewMethods
10
66
  include RubyLess
@@ -96,10 +152,13 @@ module Zena
96
152
  end
97
153
 
98
154
  safe_method :params => ParamsDictionary
155
+ safe_method :aparams => AParamsDictionary
156
+ safe_method :hparams => HParamsDictionary
157
+
99
158
  safe_method :now => {:method => 'Time.now', :class => Time}
100
159
  safe_method :string_hash => {:method => 'StringHash.new', :class => StringHash}
101
160
  safe_method [:string_hash, Hash] => {:method => 'StringHash.from_hash', :class => StringHash}
102
- safe_method [:h, String] => {:class => String, :nil => true}
161
+ safe_method [:h, String] => {:class => String, :accept_nil => true}
103
162
  safe_method_for String, [:gsub, Regexp, String] => {:class => String, :pre_processor => true}
104
163
  safe_method_for String, :upcase => {:class => String, :pre_processor => true}
105
164
  safe_method_for String, :strip => {:class => String, :pre_processor => true}
@@ -107,8 +166,8 @@ module Zena
107
166
  safe_method_for String, :url_name => {:class => String, :pre_processor => true, :method => :url_name}
108
167
  safe_method_for String, :to_i => {:class => Number, :pre_processor => true}
109
168
  safe_method_for String, :to_s => {:class => String, :pre_processor => true}
110
- safe_method_for String, [:limit, Number] => {:class => String, :pre_processor => true}
111
- safe_method_for String, [:limit, Number, String] => {:class => String, :pre_processor => true}
169
+ safe_method_for String, [:limit, Number] => {:class => String, :pre_processor => true, :html_safe => true}
170
+ safe_method_for String, [:limit, Number, String] => {:class => String, :pre_processor => true, :html_safe => true}
112
171
  safe_method_for String, :to_f => {:class => Number, :pre_processor => true}
113
172
  safe_method_for String, :to_json => {:class => String, :pre_processor => true}
114
173
  safe_method_for String, [:split, String] => {:class => [String], :pre_processor => true}
@@ -120,6 +179,8 @@ module Zena
120
179
  safe_method_for Number, :fmt => {:class => String, :pre_processor => true}
121
180
  safe_method_for Number, [:fmt, Number] => {:class => String, :pre_processor => true}
122
181
 
182
+ safe_method_for Range, :to_a => {:class => [Number], :pre_processor => true}
183
+
123
184
  safe_method_for NilClass, :to_f => {:class => Number, :pre_processor => true}
124
185
  safe_method_for NilClass, :to_i => {:class => Number, :pre_processor => true}
125
186
  safe_method_for NilClass, :to_json => {:class => String, :pre_processor => true}
@@ -168,6 +229,14 @@ module Zena
168
229
  def zafu_max(a, b)
169
230
  [a, b].max
170
231
  end
232
+
233
+ def aparams
234
+ @aparams ||= AParamsDictionary.new(params)
235
+ end
236
+
237
+ def hparams
238
+ @hparams ||= HParamsDictionary.new(params)
239
+ end
171
240
  end # ViewMethods
172
241
 
173
242
 
@@ -65,7 +65,9 @@ module Zena
65
65
  if text[0..0] == ' '
66
66
  text = "\n\n#{text}"
67
67
  end
68
- res = ZazenParser.new(text,:helper=>self).render(opt)
68
+ @zazen = ZazenParser.new(text, :helper=>self)
69
+ res = @zazen.render(opt)
70
+ @zazen = nil
69
71
  if no_p && !text.include?("\n")
70
72
  res.gsub(%r{\A<p>|</p>\Z},'')
71
73
  else
@@ -75,6 +77,14 @@ module Zena
75
77
  rescue Timeout::Error
76
78
  return %Q{<span class='parser_error'>#{_('Could not render text (Timeout error)')}</span>}
77
79
  end
80
+
81
+ def raw_content(txt)
82
+ if @zazen
83
+ @zazen.raw_content(txt)
84
+ else
85
+ txt
86
+ end
87
+ end
78
88
 
79
89
  # TODO: test
80
90
  def zazen_diff(text1, text2, opt={})
@@ -172,7 +182,7 @@ module Zena
172
182
  def make_image(opts)
173
183
  id, style, link, mode, title = opts[:id], opts[:style], opts[:link], opts[:mode], opts[:title]
174
184
  mode ||= 'std' # default mode
175
- img = opts[:node] || secure(Document) { Document.find_by_zip(id) }
185
+ img = opts[:node] || secure(Node) { Node.find_by_zip(id) }
176
186
 
177
187
  return "<span class='unknownLink'>#{_('unknown document')}</span>" unless img
178
188
 
@@ -182,11 +192,13 @@ module Zena
182
192
  title = img.summary if title == ""
183
193
 
184
194
  image = img_tag(img, :mode=>mode, :host=>opts[:host])
195
+
196
+ return "<span class='unknownLink'>#{_('invalid document')}</span>" unless image
185
197
 
186
198
  unless link
187
- if id[0..0] == "0" || !img.kind_of?(Image)
199
+ if id[0..0] == "0" || (!img.kind_of?(Image) && !(image =~ /ZAZENBLOCKCODE/))
188
200
  # if the id starts with '0' or it is not an Image, link to data
189
- link = zen_path(img, :format => img.ext)
201
+ link = zen_path(img, :format => img.prop['ext'])
190
202
  link_tag = "<a class='popup' href='#{link}' target='_blank'>"
191
203
  end
192
204
  end
data/lib/zena.rb CHANGED
@@ -315,6 +315,7 @@ EXT_TYPE = [
315
315
  [ "jpe" , "image/jpeg" ],
316
316
  [ "jpeg" , "image/jpeg" ],
317
317
  [ "js" , "application/x-javascript" ],
318
+ [ "js" , "text/javascript" ],
318
319
  [ "kar" , "audio/midi" ],
319
320
  [ "latex" , "application/x-latex" ],
320
321
  [ "lha" , "application/octet-stream" ],
@@ -18,27 +18,10 @@ Grid.log = function(what, msg) {
18
18
  }
19
19
 
20
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
21
  var row = cell.up('tr')
30
22
  var table = row.up('table')
31
23
  var grid = table.grid
32
- if (prev.value == val.value) return;
33
- if (cell.orig_value == val.value) {
34
- cell.removeClassName('changed')
35
- if (row.select('.changed').length == 0) {
36
- row.removeClassName('changed')
37
- }
38
- } else {
39
- cell.addClassName('changed')
40
- row.addClassName('changed')
41
- }
24
+
42
25
  var pos = Grid.pos(cell)
43
26
 
44
27
  var attr, id
@@ -53,6 +36,34 @@ Grid.changed = function(cell, val, prev, skip_html) {
53
36
  id = Grid.buildObj(grid, row)
54
37
  }
55
38
  }
39
+
40
+ if (!skip_html) {
41
+ var show_h = grid.show[attr] || {}
42
+ if (cell.hasAttribute('data-v')) cell.setAttribute('data-v', val.value)
43
+ cell.innerHTML = show_h[val.value] || val.show
44
+ }
45
+
46
+ if (grid.onChange) {
47
+ // onChange can be used to recompute total (so cell value should be set first)
48
+ var v = val.value
49
+ val = grid.onChange(cell, val, attr)
50
+ if (!val) return
51
+ if (v != val.value) {
52
+ cell.innerHTML = val.show
53
+ if (cell.hasAttribute('data-v')) cell.setAttribute('data-v', val.value)
54
+ }
55
+ }
56
+
57
+ if (prev.value == val.value) return;
58
+ if (cell.orig_value == val.value) {
59
+ cell.removeClassName('changed')
60
+ if (row.select('.changed').length == 0) {
61
+ row.removeClassName('changed')
62
+ }
63
+ } else {
64
+ cell.addClassName('changed')
65
+ row.addClassName('changed')
66
+ }
56
67
 
57
68
  var change = {
58
69
  id: id,
@@ -102,27 +113,28 @@ Grid.buildObj = function(grid, row) {
102
113
  }
103
114
 
104
115
  Grid.closeCell = function(e) {
105
- if (Grid.in_paste) return
116
+ if (Grid.no_blur) return
106
117
  var cell = e.tagName ? e : e.element().up()
107
118
  var table = cell.up('table')
108
119
  var prev = cell.prev_value
109
120
  var val = Grid.getValue(cell)
110
121
 
111
- if (table.grid.list_name) {
122
+ if (table.grid.is_list) {
112
123
  if (val.value == 'on') {
113
124
  cell.addClassName('on')
114
125
  } else {
115
126
  cell.removeClassName('on')
116
127
  }
117
- } else {
118
- cell.removeClassName('input')
119
128
  }
129
+ cell.removeClassName('input')
120
130
 
121
131
  Grid.changed(cell, val, prev)
122
132
 
123
133
  if (table.grid.input) {
124
134
  // single attribute table, serialize in input field
125
135
  table.grid.input.value = Grid.serialize(table)
136
+ } else if (table.grid.autoSave) {
137
+ Grid.save(table.id)
126
138
  }
127
139
  }
128
140
 
@@ -150,9 +162,9 @@ Grid.paste = function(event) {
150
162
  bottom: "<textarea style='position:fixed; top:0; left:10100px;' id='grid_p_" + table.grid.id + "'></textarea>"
151
163
  });
152
164
  paster = $("grid_p_" + table.grid.id);
153
- Grid.in_paste = true // prevent original input blur
165
+ Grid.no_blur = true // prevent original input blur
154
166
  paster.focus();
155
- Grid.in_paste = false
167
+ Grid.no_blur = false
156
168
  }
157
169
  setTimeout(function() {
158
170
  var text
@@ -264,6 +276,11 @@ Grid.keydown = function(event) {
264
276
  return false
265
277
  } else if (cell.childElements().first().tagName == 'SELECT' && event.shiftKey) {
266
278
  return
279
+ } else if (grid.autoSave) {
280
+ // this will close cell
281
+ var i = cell.childElements().first()
282
+ if (i) i.blur()
283
+ return
267
284
  }
268
285
  var pos = Grid.pos(cell);
269
286
  // go to next row
@@ -306,18 +323,39 @@ Grid.keydown = function(event) {
306
323
  }
307
324
 
308
325
  Grid.isReadOnly = function(cell) {
309
- return cell.select('a').length > 0 || cell.getAttribute('data-m') == 'r'
326
+ return cell.select('a').length > 0 || cell.getAttribute('data-m') == 'r' || cell.hasClassName('action')
310
327
  }
311
328
 
312
- Grid.openCell = function(cell) {
313
- if (cell.hasClassName('input') || Grid.isReadOnly(cell)) return;
329
+ Grid.closeCheckbox = function() {
330
+ if (Grid.need_close) {
331
+ Grid.closeCell(Grid.need_close)
332
+ Grid.need_close = false
333
+ }
334
+ }
335
+ Grid.openCell = function(cell, get_next) {
336
+ var get_next = get_next == undefined ? true : get_next
337
+ Grid.closeCheckbox()
338
+
339
+ if (cell.hasClassName('input')) return;
340
+ if (Grid.isReadOnly(cell)) {
341
+ if (get_next) {
342
+ var n = Element.next(cell) || cell.up().nextSiblings().first().childElements()[0]
343
+ if (n) {
344
+ return Grid.openCell(n)
345
+ } else {
346
+ return false
347
+ }
348
+ } else {
349
+ return false
350
+ }
351
+ }
314
352
  var val = Grid.getValue(cell)
315
353
  cell.prev_value = val;
316
354
 
317
355
  var value = val.value
318
356
  var table = cell.up('table')
319
357
 
320
- if (table.grid.list_name) {
358
+ if (table.grid.is_list) {
321
359
  if (value == 'on') {
322
360
  cell.setAttribute('data-v', 'off')
323
361
  } else {
@@ -353,16 +391,23 @@ Grid.openCell = function(cell) {
353
391
  input = cell.childElements()[0]
354
392
  input.value = value
355
393
  }
394
+
356
395
  input.setStyle({
357
396
  width: w + 'px',
358
397
  height: h + 'px'
359
398
  })
360
- input.observe('blur', Grid.closeCell)
399
+
400
+ if (input.type == 'checkbox') {
401
+ Grid.need_close = cell
402
+ } else {
403
+ input.observe('blur', Grid.closeCell)
404
+ }
361
405
  input.observe('keydown', Grid.keydown)
362
406
  input.observe('paste', Grid.paste)
363
407
  input.focus()
364
408
  input.select()
365
409
  }
410
+ return true
366
411
  }
367
412
 
368
413
  Grid.click = function(event) {
@@ -371,13 +416,19 @@ Grid.click = function(event) {
371
416
  var table = cell.up('table')
372
417
  if (row.hasClassName('action')) {
373
418
  Grid.action(event, cell, row, true)
419
+ Event.stop(event)
374
420
  } else if (cell.hasClassName('action')) {
375
421
  Grid.action(event, cell, row, false)
422
+ Event.stop(event)
376
423
  } else if (cell.tagName == 'TH' && !table.grid.attr_name) {
377
424
  // sort
378
425
  Grid.sort(cell)
426
+ Event.stop(event)
379
427
  } else {
380
- Grid.openCell(cell)
428
+ if (event.element().tagName == 'INPUT') return;
429
+ if (Grid.openCell(cell, false)) {
430
+ Event.stop(event)
431
+ }
381
432
  }
382
433
  }
383
434
 
@@ -438,10 +489,15 @@ Grid.copy = function(cell) {
438
489
 
439
490
  Grid.addRow = function(table, row) {
440
491
  // insert row below
441
- var row_str = '<tr>';
442
- var cells = row.childElements();
443
- for (var i = 0; i < cells.length -1; i++) {
444
- row_str = row_str + '<td></td>';
492
+ var row_str = '<tr>'
493
+ var grid = table.grid
494
+ if (grid.newRow) {
495
+ row_str = row_str + $(grid.newRow).innerHTML
496
+ } else {
497
+ var cells = row.childElements()
498
+ for (var i = 0; i < cells.length -1; i++) {
499
+ row_str = row_str + '<td></td>'
500
+ }
445
501
  }
446
502
  row_str = row_str + Grid.Buttons(table.grid) + '</tr>';
447
503
  row.insert({
@@ -723,26 +779,37 @@ Grid.make = function(table, opts) {
723
779
  if (table.grid) return;
724
780
  Grid.grid_c++;
725
781
  Grid.grids[Grid.grid_c] = table;
726
- table.grid = {
782
+ var grid = {
727
783
  changes: [],
728
784
  id: Grid.grid_c,
729
- helper_id: table.getAttribute('data-helper'),
730
- fdate: table.getAttribute('data-fdate'),
785
+ helper_id: opts.helper || table.getAttribute('data-helper'),
786
+ fdate: opts.fdate || table.getAttribute('data-fdate'),
787
+ newRow: opts.newRow,
731
788
  counter: 0, // Used to create dom_ids for new objects
732
789
  onSuccess: opts.onSuccess,
733
790
  onFailure: opts.onFailure || Grid.onFailure,
734
791
  onStart: opts.onStart || Grid.onStart,
792
+ onChange: opts.onChange,
793
+ // Save on cell close
794
+ autoSave: opts.autoSave || false,
795
+ sort: opts.sort == undefined ? true : opts.sort,
735
796
  add: opts.add || opts.add == undefined,
736
797
  remove: opts.remove || opts.remove == undefined,
737
798
  keydown: opts.keydown,
799
+ show: opts.show || {},
738
800
  };
801
+ table.grid = grid
802
+ grid.table = table
739
803
 
740
804
  // Detect type.
741
- table.grid.attr_name = table.getAttribute('data-a');
742
- table.grid.list_name = table.getAttribute('data-l');
805
+ grid.attr_name = table.getAttribute('data-a')
806
+ grid.list_name = table.getAttribute('data-l')
807
+ // Reverse list name (update columns instead of rows)
808
+ grid.rlist_name = table.getAttribute('data-r')
809
+ grid.is_list = grid.list_name || grid.rlist_name
743
810
 
744
811
  var empty = false;
745
- if (table.grid.attr_name && table.select('th').length == 0) {
812
+ if (grid.attr_name && table.select('th').length == 0) {
746
813
  empty = true;
747
814
  var msg = table.getAttribute('data-msg') || "type to edit";
748
815
  table.innerHTML = "<tr><th>" + msg + "</th></tr><tr><td></td></tr>";
@@ -752,27 +819,34 @@ Grid.make = function(table, opts) {
752
819
  Grid.addButtons(table)
753
820
 
754
821
 
755
- if (table.grid.attr_name) {
822
+ if (grid.attr_name) {
823
+ table.insert({
824
+ after: "<p class='grid_btn'><a class='undo' href='javascript:' onclick='Grid.undo(" + Grid.grid_c + ")'>undo</a></p>"
825
+ });
756
826
  // If we have an attr_name, rows and columns are
757
827
  // serialized as json in a single field.
758
828
  table.insert({
759
- after: "<input type='hidden' id='grid_a_" + Grid.grid_c + "' name='" + table.grid.attr_name + "'/>"
829
+ after: "<input type='hidden' id='grid_a_" + Grid.grid_c + "' name='" + grid.attr_name + "'/>"
760
830
  });
761
- table.grid.input = $("grid_a_" + Grid.grid_c);
762
- if (!empty) table.grid.input.value = Grid.serialize(table);
831
+ grid.input = $("grid_a_" + Grid.grid_c);
832
+ if (!empty) grid.input.value = Grid.serialize(table);
763
833
  } else {
834
+ // Otherwise each row is a new object and each column
835
+ // corresponds to a different attribute (defined in the
836
+ // 'th' of the table).
764
837
  var rows = table.select('tr')
765
838
  for (var i = 1; i < rows.length; i++) {
766
- if (!rows[i].id) {
839
+ if (rows[i].id || rows[i].getAttribute('data-m') == 'r') {
840
+ // do not create object
841
+ } else {
767
842
  Grid.buildObj(table.grid, rows[i])
768
843
  }
769
844
  }
770
- // Otherwise each row is a new object and each column
771
- // corresponds to a different attribute (defined in the
772
- // 'th' of the table).
773
- table.insert({
774
- after: "<p class='grid_btn'><a class='save' href='javascript:' onclick='Grid.save(" + Grid.grid_c + ")'>save</a> <a class='undo' href='javascript:' onclick='Grid.undo(" + Grid.grid_c + ")'>undo</a></p>"
775
- });
845
+ if (!grid.autoSave) {
846
+ table.insert({
847
+ after: "<p class='grid_btn'><a class='save' href='javascript:' onclick='Grid.save(" + Grid.grid_c + ")'>save</a> <a class='undo' href='javascript:' onclick='Grid.undo(" + Grid.grid_c + ")'>undo</a></p>"
848
+ });
849
+ }
776
850
  }
777
851
 
778
852
  table.observe('click', Grid.click);
@@ -795,22 +869,22 @@ Grid.clearChanges = function(list, id) {
795
869
  }
796
870
 
797
871
  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)
872
+ return $$('#'+$(elem).id+' .changed').length > 0
802
873
  }
803
874
 
804
-
805
875
  Grid.save = function(grid_id) {
806
876
  // do not run on GUI thread
807
877
  setTimeout(function() {
808
- var table = Grid.grids[grid_id]
878
+ Grid.closeCheckbox()
879
+ var table = Grid.grids[grid_id] || $(grid_id)
809
880
  var grid = table.grid
810
881
  var data = Grid.compact(grid.changes)
811
882
  if (grid.list_name) {
812
883
  data = Grid.dataForList(grid, data)
884
+ } else if (grid.rlist_name) {
885
+ data = Grid.dataForReverseList(grid, data)
813
886
  }
887
+
814
888
  var todo_count = data.keys().length
815
889
  var done_count = 0
816
890
  if (grid.onStart) {
@@ -822,8 +896,9 @@ Grid.save = function(grid_id) {
822
896
  operations.put = (operations.put || 0) + 1
823
897
  }
824
898
  })
825
- if (!grid.onStart(operations)) return
899
+ if (!grid.onStart(operations, data)) return
826
900
  }
901
+
827
902
  data.each(function(pair) {
828
903
  var id = pair.key
829
904
  var changes = pair.value
@@ -833,6 +908,7 @@ Grid.save = function(grid_id) {
833
908
  attrs['node['+pair.key+']'] = pair.value
834
909
  }
835
910
  })
911
+
836
912
  if (changes._new) {
837
913
  new Ajax.Request('/nodes', {
838
914
  parameters: attrs,
@@ -864,7 +940,7 @@ Grid.save = function(grid_id) {
864
940
  method: 'post'
865
941
  });
866
942
  } else {
867
- new Ajax.Request('/nodes/' + id.replace('id_',''), {
943
+ new Ajax.Request('/nodes/' + id.replace(/^[^0-9]+/,''), {
868
944
  parameters: attrs,
869
945
  onSuccess: function(transport) {
870
946
  done_count++
@@ -884,7 +960,9 @@ Grid.save = function(grid_id) {
884
960
  }
885
961
 
886
962
  Grid.undo = function(grid_id, skip_undone) {
887
- var table = Grid.grids[grid_id]
963
+ Grid.closeCheckbox()
964
+
965
+ var table = Grid.grids[grid_id] || $(grid_id)
888
966
  var grid = table.grid
889
967
  var changes = grid.changes
890
968
  var last = changes.last()
@@ -911,7 +989,7 @@ Grid.undo = function(grid_id, skip_undone) {
911
989
  [cell, cell.up()].invoke('addClassName', 'changed')
912
990
  }
913
991
  cell.addClassName('undone')
914
- if (grid.list_name) {
992
+ if (grid.is_list) {
915
993
  cell.setAttribute('data-v', value)
916
994
  if (value == 'on') {
917
995
  cell.addClassName('on')
@@ -927,6 +1005,11 @@ Grid.undo = function(grid_id, skip_undone) {
927
1005
  changes.pop()
928
1006
  }
929
1007
 
1008
+ if (grid.input) {
1009
+ // single attribute table, serialize in input field
1010
+ grid.input.value = Grid.serialize(table)
1011
+ }
1012
+
930
1013
  if (!skip_undone) {
931
1014
  setTimeout(function() {
932
1015
  table.select('.undone').invoke('removeClassName', 'undone')
@@ -965,6 +1048,9 @@ Grid.test = function() {
965
1048
  // detects which rows have changes and builds the full list of
966
1049
  // ids in the format some_relation_ids:"123,345,888,432". Other fields
967
1050
  // are kept as is.
1051
+ // If update_relation_for is 'column', the list of ids is reversed (they
1052
+ // contain the row ids) and we build operation for setting the relation
1053
+ // with the column id).
968
1054
  Grid.dataForList = function(grid, data) {
969
1055
  var res = {}
970
1056
  var list_name = grid.list_name
@@ -999,6 +1085,43 @@ Grid.dataForList = function(grid, data) {
999
1085
  return $H(res)
1000
1086
  }
1001
1087
 
1088
+ Grid.dataForReverseList = function(grid, data) {
1089
+ var res = {}
1090
+ var rlist_name = grid.rlist_name
1091
+ // {"id_184": {"147": "on"}, "id_193": {"147": "on", "146": "off"}}
1092
+ data.each(function(pair) {
1093
+ var def = pair.value
1094
+ for (var key in def) {
1095
+ if (parseInt(key) + '' == key) {
1096
+ // number key = there is a change in the list
1097
+ var col = res[key]
1098
+ if (!col) {
1099
+ // build full column update operation
1100
+ col = {id:key}
1101
+ res[key] = col
1102
+ var list = []
1103
+ var pos = grid.pos[key]
1104
+ var rows = grid.table.childElements()[0].select('tr')
1105
+ for(var i = rows.length - 1; i >= 0; i--) {
1106
+ var row = rows[i]
1107
+ if (row.getAttribute('data-m') != 'r') {
1108
+ var cell = rows[i].childElements()[pos]
1109
+ if (cell && cell.hasClassName('on')) {
1110
+ list.push(row.id.sub(/^[^\d]+/,''))
1111
+ }
1112
+ }
1113
+ }
1114
+ col[rlist_name] = list.join(',')
1115
+ }
1116
+ } else {
1117
+ // ignore other attributes
1118
+ }
1119
+ }
1120
+ })
1121
+
1122
+ return $H(res)
1123
+ }
1124
+
1002
1125
  Grid.notify = function(table, changes) {
1003
1126
  var rows = table.childElements()[0].select('tr')
1004
1127
  var grid = table.grid
@@ -1011,10 +1134,11 @@ Grid.notify = function(table, changes) {
1011
1134
  } else {
1012
1135
  row = $(obj_id)
1013
1136
  }
1014
- var cells = row.childElements()
1137
+
1015
1138
  var change = changes[obj_id]
1016
1139
  for (var attr in change) {
1017
1140
  if (attr == 'id') continue
1141
+
1018
1142
  if (attr == grid.list_name) {
1019
1143
  var list_on = change[attr].split(/,/)
1020
1144
  var cells = row.childElements()
@@ -1031,11 +1155,36 @@ Grid.notify = function(table, changes) {
1031
1155
  }
1032
1156
  cell.prev_value = undefined
1033
1157
  cell.removeClassName('error')
1158
+ if (cell.hasClassName('changed')) cell.addClassName('saved')
1034
1159
  cell.removeClassName('changed')
1035
- cell.addClassName('saved')
1160
+ }
1161
+ }
1162
+ } else if (attr == grid.rlist_name) {
1163
+ var list_on = change[attr].split(/,/)
1164
+ var pos = grid.pos[obj_id.sub(/^[^\d]+/,'')]
1165
+ var rows = grid.table.childElements()[0].select('tr')
1166
+ for(var i = rows.length - 1; i >= 0; i--) {
1167
+ var row = rows[i]
1168
+ if (row.getAttribute('data-m') != 'r') {
1169
+ var cell = row.childElements()[pos]
1170
+ if (list_on.indexOf(row.id.sub(/^[^\d]+/,'')) >= 0) {
1171
+ cell.orig_value = 'on'
1172
+ cell.setAttribute('data-v', 'on')
1173
+ } else {
1174
+ cell.orig_value = 'off'
1175
+ cell.setAttribute('data-v', 'off')
1176
+ }
1177
+ cell.prev_value = undefined
1178
+ cell.removeClassName('error')
1179
+ if (cell.hasClassName('changed')) cell.addClassName('saved')
1180
+ cell.removeClassName('changed')
1181
+ if (row.select('.changed').length == 0) {
1182
+ row.removeClassName('changed')
1183
+ }
1036
1184
  }
1037
1185
  }
1038
1186
  } else {
1187
+ var cells = row.childElements()
1039
1188
  var cell
1040
1189
  var i = pos[attr]
1041
1190
  if (i == undefined) continue
@@ -1059,7 +1208,7 @@ Grid.notify = function(table, changes) {
1059
1208
  // later
1060
1209
  setTimeout(function() {
1061
1210
  table.select('.saved').invoke('removeClassName', 'saved')
1062
- }, 1000)
1211
+ }, 600)
1063
1212
  }
1064
1213
 
1065
1214
  Grid.simulateClick = function(l) {