formize 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.12
1
+ 0.0.13
@@ -1,149 +1,137 @@
1
1
  /*jslint devel: true, browser: true, sloppy: true, vars: true, white: true, maxerr: 50, indent: 2 */
2
2
 
3
- var Formize = {
4
- uniqueID: function() {
5
- var uid = 'u'+((new Date()).getTime() + "" + Math.floor(Math.random() * 1000000)).substr(0, 18);
6
- return uid;
7
- }
8
- };
9
-
10
- Formize.Overlay = {
11
-
12
- count: 0,
13
-
14
- add: function (body) {
15
- var overlay = $('#overlay')[0];
16
- if (overlay === null || overlay === undefined) {
17
- overlay = $(document.createElement('div'));
18
- overlay.attr({id: 'overlay', style: 'position:fixed; top:0; left: 0; display: none'});
19
- $('body').append(overlay);
20
- this.resize();
21
- overlay.fadeIn('fast');
22
- }
23
- this.count += 1;
24
- overlay.css('z-index', this.zIndex());
25
- return overlay;
26
- },
27
-
28
- resize: function () {
29
- var height = $(document).height(), width = $(document).width();
30
- var overlay = $('#overlay');
31
- overlay.css({width: width+'px', height: height+'px'});
32
- },
33
-
34
- remove: function() {
35
- this.count -= 1;
36
- var overlay = $('#overlay');
37
- if (overlay !== null) {
38
- if (this.count <= 0) {
39
- overlay.fadeOut(400, function() { $(this).remove(); });
40
- } else {
41
- overlay.css('z-index', this.zIndex());
42
- }
3
+ (function ($) {
4
+ // Replaces `$(selector).live("ready", handler)` which don't work
5
+ // It rebinds automatically after each ajax request all not-binded.
6
+ $.behaviours = [];
7
+ $.behave = function (selector, eventType, handler) {
8
+ if (eventType == "load") {
9
+ $(document).ready(function(event) {
10
+ $(selector).each(handler);
11
+ $(selector).prop('alreadyBound', true);
12
+ });
13
+ $.behaviours.push({selector: selector, handler: handler});
14
+ } else {
15
+ $(selector).live(eventType, handler);
43
16
  }
44
- return this.count;
45
- },
46
-
47
- // Computes a big z-index with interval in order to intercalate dialogs
48
- zIndex: function() {
49
- return (10*this.count + 10000);
50
17
  }
51
- };
52
-
53
-
18
+ // Rebinds unbound elements on each ajax request.
19
+ $(document).ajaxStop(function () {
20
+ for (var behaviour in $.behaviours) {
21
+ behaviour = $.behaviours[behaviour];
22
+ $(behaviour.selector).each(function(index, element){
23
+ if ($(element).prop('alreadyBound') !== true) {
24
+ behaviour.handler.call($(element));
25
+ $(element).prop('alreadyBound', true);
26
+ }
27
+ });
28
+ }
29
+ });
54
30
 
55
- Formize.Dialog = {
56
31
 
57
- // Opens a div like a virtual popup
58
- open: function (url, updated, ratio) {
59
- var height = $(document).height(), width = $(document).width();
60
- var dialog_id = 'dialog'+Formize.Overlay.count;
61
- if (isNaN(ratio)) { ratio = 0.6; }
62
-
63
- Formize.Overlay.add();
32
+ $.ajaxDialogCount = 0;
64
33
 
34
+ $.ajaxDialog = function (url, settings) {
35
+ var frame_id = "dialog-" + $.ajaxDialogCount, width = $(document).width();
36
+ var defaultSettings = {
37
+ header: "X-Return-Code"
38
+ };
39
+ if (settings === null || settings === undefined) { settings = {}; }
40
+ settings = $.extend({}, defaultSettings, settings);
65
41
  $.ajax(url, {
66
- data: {dialog: dialog_id},
42
+ data: {dialog: frame_id},
67
43
  success: function(data, textStatus, jqXHR) {
68
- var dialog = $(document.createElement('div'));
69
- dialog.attr({id: dialog_id, 'data-ratio': ratio, 'data-dialog-update': updated, flex: '1', 'class': 'dialog', style: 'z-index:'+(Formize.Overlay.zIndex()+1)+'; position:fixed; display: none;'});
70
- $('body').append(dialog);
71
- dialog.html(data);
72
- Formize.Dialog.resize(dialog);
73
- dialog.fadeIn(400, function() {
74
- $(document).trigger("dom:update", dialog.attr('id'));
75
- });
44
+ var frame = $(document.createElement('div')), h1, title=null;
45
+ frame.attr({id: frame_id, 'class': 'dialog ajax-dialog', style: 'display:none;'});
46
+ $('body').append(frame);
47
+ frame.html(data);
48
+ frame.prop("dialogSettings", settings);
49
+ frame.dialog({
50
+ autoOpen: false,
51
+ show: 'fade',
52
+ modal: true,
53
+ width: (width > 1024 ? width *0.6 : width*0.9),
54
+ minWidth: (width*0.3 < 150 ? 150 : width*0.3)
55
+ });
56
+ $.ajaxDialogInitialize(frame);
57
+ frame.dialog("open");
76
58
  },
77
59
  error: function(jqXHR, textStatus, errorThrown) {
78
60
  alert("FAILURE (Error "+textStatus+"): "+errorThrown);
79
- Formize.Overlay.remove();
61
+ var frame = $("#" + frame_id);
62
+ frame.dialog("close");
63
+ frame.remove();
80
64
  }
81
65
  });
82
- },
83
-
84
- resize: function (dialog) {
85
- var width = $(window).width(), height = $(window).height();
86
- var ratio = parseFloat(dialog.attr('data-ratio'));
87
- var w = dialog.width();
88
- var h = dialog.height();
89
- if (ratio > 0) {
90
- w = ratio*width;
91
- h = ratio*height;
66
+ $.ajaxDialogCount += 1;
67
+ };
68
+
69
+ $.ajaxDialogInitialize = function(frame) {
70
+ var frame_id = frame.attr("id");
71
+ var title = frame.prop("dialogSettings")["title"];
72
+ if (title === null || title === undefined) {
73
+ var h1 = $("#" + frame_id + " h1");
74
+ if (h1[0] !== null && h1[0] !== undefined) {
75
+ title = h1.text()
76
+ h1.remove();
77
+ }
92
78
  }
93
- dialog.animate({left: ((width-w)/2)+'px', top: ((height-h)/2)+'px', width: w+'px', height: h+'px'});
94
- return true;
95
- },
96
-
97
- // Close a virtual popup
98
- close: function(dialog) {
99
- dialog = $('#'+dialog);
100
- dialog.fadeOut(400, function() { $(this).remove(); });
101
- Formize.Overlay.remove();
102
- return true;
103
- },
104
-
105
- submitForm: function(form) {
106
- var form = $(this);
107
- var dialog_id = form.attr('data-dialog');
108
- var dialog = $('#'+dialog_id);
109
-
79
+ frame.dialog("option", "title", title);
80
+
81
+ $("#" + frame_id + " form").each(function (index, form) {
82
+ $(form).attr('data-dialog', frame_id);
83
+ });
84
+ };
85
+
86
+ $.submitAjaxForm = function () {
87
+ var form = $(this);
88
+ var frame_id = form.attr('data-dialog');
89
+ var frame = $('#'+frame_id);
90
+ var settings = frame.prop("dialogSettings");
91
+
110
92
  var field = $(document.createElement('input'));
111
- field.attr({ type: 'hidden', name: 'dialog', value: dialog_id });
93
+ field.attr({ type: 'hidden', name: 'dialog', value: frame_id });
112
94
  form.append(field);
113
95
 
114
- $.ajax(form.attr('action'), {
115
- type: form.attr('method') || 'POST',
116
- data: form.serialize(),
117
- success: function(data, textStatus, request) {
118
- var record_id = request.getResponseHeader("X-Saved-Record-Id");
119
- if (record_id === null) {
120
- // No return => validation error
121
- dialog.html(request.responseText);
122
- $(document).trigger("dom:update", dialog.attr('id'));
123
- } else {
124
- // Refresh element with its refresh URL
125
- var updated_id = '#'+dialog.attr('data-dialog-update'), updated = $(updated_id);
126
- if (updated[0] !== undefined) {
127
- var url = updated.attr('data-refresh');
128
- $.ajax(url, {
129
- data: {selected: record_id},
130
- success: function(data2, textStatus2, request2) {
131
- updated.replaceWith(request2.responseText);
132
- $(document).trigger("dom:update");
133
- $(updated_id+' input').trigger("emulated:change");
134
- }
135
- });
96
+ $.ajax(form.attr('action'), {
97
+ type: form.attr('method') || 'POST',
98
+ data: form.serialize(),
99
+ success: function(data, textStatus, request) {
100
+ var returnCode = request.getResponseHeader(settings["header"])
101
+ var returns = settings["returns"], unknownReturnCode = true;
102
+ for (var code in returns) {
103
+ if (returnCode == code && $.isFunction(returns[code])) {
104
+ returns[code].call(form, frame, data, textStatus, request);
105
+ unknownReturnCode = false;
106
+ $.ajaxDialogInitialize(frame);
107
+ break;
136
108
  }
137
- // Close dialog
138
- Formize.Dialog.close(dialog_id);
139
109
  }
140
- }
141
- });
110
+ if (unknownReturnCode) {
111
+ if ($.isFunction(settings["defaultReturn"])) {
112
+ settings["defaultReturn"].call(form, frame);
113
+ } else {
114
+ alert("FAILURE (Unknown return code for header " + settings["header"] + "): " + returnCode);
115
+ }
116
+ }
117
+ },
118
+ error: function(jqXHR, textStatus, errorThrown) {
119
+ alert("FAILURE (Error "+textStatus+"): "+errorThrown);
120
+ var frame = $("#" + frame_id);
121
+ frame.dialog("close");
122
+ frame.remove();
123
+ // if ($.isFunction(settings["error"])) { settings["error"].call(form, frame, jqXHR, textStatus, errorThrown); }
124
+ }
125
+ });
142
126
  return false;
143
- }
127
+ };
128
+
129
+ // Submits dialog forms
130
+ $.behave(".ajax-dialog form[data-dialog]", "submit", $.submitAjaxForm);
144
131
 
145
- };
132
+ })(jQuery);
146
133
 
134
+ var Formize = {};
147
135
 
148
136
  Formize.refreshDependents = function (event) {
149
137
  var element = $(this);
@@ -158,12 +146,10 @@ Formize.refreshDependents = function (event) {
158
146
  $.ajax(url, {
159
147
  data: params,
160
148
  success: function(data, textStatus, response) {
161
- // alert("Success: "+response.responseText);
162
149
  $(item).replaceWith(response.responseText);
163
- $(document).trigger("dom:update");
164
150
  },
165
151
  error: function(jqXHR, textStatus, errorThrown) {
166
- alert("FAILURE (Error "+textStatus+"): "+errorThrown);
152
+ alert("FAILURE (Error "+textStatus+"): "+errorThrown);
167
153
  }
168
154
  });
169
155
  }
@@ -176,88 +162,55 @@ Formize.refreshDependents = function (event) {
176
162
  Formize.Toggles = {
177
163
 
178
164
  ifChecked: function () {
179
- if (this.checked) {
180
- $($(this).attr('data-show')).slideDown();
181
- $($(this).attr('data-hide')).slideUp();
182
- } else {
183
- $($(this).attr('data-show')).slideUp();
184
- $($(this).attr('data-hide')).slideDown();
185
- }
165
+ if (this.checked) {
166
+ $($(this).attr('data-show')).slideDown();
167
+ $($(this).attr('data-hide')).slideUp();
168
+ } else {
169
+ $($(this).attr('data-show')).slideUp();
170
+ $($(this).attr('data-hide')).slideDown();
171
+ }
186
172
  }
187
173
 
188
174
  }
189
175
 
190
176
 
191
- /**
192
- * Special method which is a sharthand to bind every element
193
- * concerned by the selector now and in the future. It correspond
194
- * to a lack of functionnality of jQuery on 'load' events.
195
- */
196
- $.rebindeds = [];
197
- function behave(selector, eventType, handler) {
198
- if (eventType == "load") {
199
- $(document).ready(function(event) {
200
- $(selector).each(handler);
201
- $(selector).attr('data-already-bound', 'true');
202
- });
203
- $.rebindeds.push({selector: selector, handler:handler});
204
- } else {
205
- $(selector).live(eventType, handler);
206
- }
207
- }
208
-
209
- // Rebinds unbound elements on DOM updates.
210
- $(document).bind('dom:update', function(event, element_id) {
211
- var rebinded;
212
- for (var i=0; i<$.rebindeds.length; i++) {
213
- rebinded = $.rebindeds[i];
214
- $(rebinded.selector).each(function(x, element){
215
- if ($(element).attr('data-already-bound') !== 'true') {
216
- rebinded.handler.call($(element));
217
- $(element).attr('data-already-bound', 'true');
218
- }
219
- });
220
- }
221
- });
222
-
223
177
 
224
178
  // Initializes unroll inputs
225
- behave('input[data-unroll]', 'load', function() {
179
+ $.behave('input[data-unroll]', 'load', function() {
226
180
  var element = $(this), choices, paramName;
227
181
 
228
182
  element.unrollCache = element.val();
229
183
  element.autocompleteType = "text";
230
184
  element.valueField = $('#'+element.attr('data-value-container'))[0];
231
185
  if ($.isEmptyObject(element.valueField)) {
232
- alert('An input '+element.id+' with a "data-unroll" attribute must contain a "data-value-container" attribute');
233
- element.autocompleteType = "id";
186
+ alert('An input '+element.id+' with a "data-unroll" attribute must contain a "data-value-container" attribute');
187
+ element.autocompleteType = "id";
234
188
  }
235
189
  element.maxResize = parseInt(element.attr('data-max-resize'));
236
190
  if (isNaN(element.maxResize) || element.maxResize === 0) { element.maxResize = 64; }
237
191
  element.size = (element.unrollCache.length < 32 ? 32 : element.unrollCache.length > element.maxResize ? element.maxResize : element.unrollCache.length);
238
192
 
239
193
  element.autocomplete({
240
- source: element.attr('data-unroll'),
241
- minLength: 1,
242
- select: function(event, ui) {
243
- var selected = ui.item;
244
- element.valueField.value = selected.id;
245
- element.unrollCache = selected.label;
246
- element.attr("size", (element.unrollCache.length < 32 ? 32 : element.unrollCache.length > element.maxResize ? element.maxResize : element.unrollCache.length));
247
- $(element.valueField).trigger("emulated:change");
248
- return true;
249
- }
194
+ source: element.attr('data-unroll'),
195
+ minLength: 1,
196
+ select: function(event, ui) {
197
+ var selected = ui.item;
198
+ element.valueField.value = selected.id;
199
+ element.unrollCache = selected.label;
200
+ element.attr("size", (element.unrollCache.length < 32 ? 32 : element.unrollCache.length > element.maxResize ? element.maxResize : element.unrollCache.length));
201
+ $(element.valueField).trigger("emulated:change");
202
+ return true;
203
+ }
250
204
  });
251
205
  });
252
206
 
253
-
254
207
  // Initializes date fields
255
- behave('input[data-datepicker]', "load", function() {
208
+ $.behave('input[data-datepicker]', "load", function() {
256
209
  var element = $(this);
257
210
  var locale = element.attr("data-locale");
258
211
  var options = $.datepicker.regional[locale];
259
212
  if (element.attr("data-date-format") !== null) {
260
- options['dateFormat'] = element.attr("data-date-format");
213
+ options['dateFormat'] = element.attr("data-date-format");
261
214
  }
262
215
  options['altField'] = '#'+element.attr("data-datepicker");
263
216
  options['altFormat'] = 'yy-mm-dd';
@@ -267,49 +220,51 @@ behave('input[data-datepicker]', "load", function() {
267
220
 
268
221
  // Initializes resizable text areas
269
222
  // Minimal size is defined on default size of the area
270
- behave('textarea[data-resizable]', "load", function() {
223
+ $.behave('textarea[data-resizable]', "load", function() {
271
224
  var element = $(this);
272
225
  element.resizable({
273
- handles: "se",
274
- minHeight: element.height(),
275
- minWidth: element.width(),
276
- create: function (event, ui) { $(this).css("padding-bottom", "0px"); },
277
- stop: function (event, ui) { $(this).css("padding-bottom", "0px"); }
226
+ handles: "se",
227
+ minHeight: element.height(),
228
+ minWidth: element.width(),
229
+ create: function (event, ui) { $(this).css("padding-bottom", "0px"); },
230
+ stop: function (event, ui) { $(this).css("padding-bottom", "0px"); }
278
231
  });
279
232
  });
280
233
 
281
- // Opens a dialog for a ressource creation
282
- behave("a[data-add-item]", "click", function() {
234
+ // Opens a dialog for a resource creation
235
+ $.behave("a[data-add-item]", "click", function() {
283
236
  var element = $(this);
284
- var list_id = element.attr('data-add-item');
237
+ var list_id = '#'+element.attr('data-add-item'), list = $(list_id);
285
238
  var url = element.attr('href');
286
- Formize.Dialog.open(url, list_id);
287
- return false;
288
- });
289
-
290
- // Closes a dialog
291
- behave("a[data-close-dialog]", "click", function() {
292
- var dialog_id = element.attr('data-close-dialog');
293
- Formize.Dialog.close(dialog_id);
239
+ $.ajaxDialog(url, {
240
+ returns: {
241
+ success: function (frame, data, textStatus, request) {
242
+ var record_id = request.getResponseHeader("X-Saved-Record-Id");
243
+ if (list[0] !== undefined) {
244
+ $.ajax(list.attr('data-refresh'), {
245
+ data: {selected: record_id},
246
+ success: function(data, textStatus, request) {
247
+ list.replaceWith(request.responseText);
248
+ $(list_id + ' input').trigger("emulated:change");
249
+ }
250
+ });
251
+ }
252
+ frame.dialog("close");
253
+ },
254
+ invalid: function (frame, data, textStatus, request) {
255
+ frame.html(request.responseText);
256
+ }
257
+ },
258
+ });
294
259
  return false;
295
260
  });
296
261
 
297
- // Submits dialog forms
298
- behave("form[data-dialog]", "submit", Formize.Dialog.submitForm);
299
-
300
262
  // Refresh dependents on changes
301
- behave("*[data-dependents]", "change", Formize.refreshDependents);
302
- behave("*[data-dependents]", "emulated:change", Formize.refreshDependents);
263
+ $.behave("*[data-dependents]", "change", Formize.refreshDependents);
264
+ $.behave("*[data-dependents]", "emulated:change", Formize.refreshDependents);
303
265
  // Compensate for changes made with keyboard
304
- behave("select[data-dependents]", "keypress", Formize.refreshDependents);
266
+ $.behave("select[data-dependents]", "keypress", Formize.refreshDependents);
305
267
 
306
- behave("input[data-show], input[data-hide]", "load", Formize.Toggles.ifChecked);
307
- behave("input[data-show], input[data-hide]", "change", Formize.Toggles.ifChecked);
308
-
309
- // Resizes the overlay automatically
310
- $(window).resize(function() {
311
- Formize.Overlay.resize();
312
- $('.dialog').each(function(i, dialog) {
313
- Formize.Dialog.resize($(dialog));
314
- });
315
- });
268
+ // Hide/show blocks depending on check boxes
269
+ $.behave("input[data-show], input[data-hide]", "load", Formize.Toggles.ifChecked);
270
+ $.behave("input[data-show], input[data-hide]", "change", Formize.Toggles.ifChecked);
@@ -5,6 +5,30 @@ module Formize
5
5
  def self.included(base)
6
6
  base.extend(ClassMethods)
7
7
  end
8
+
9
+
10
+ # Adds method to provides a default response for create/update actions
11
+ # It saves the record/resource and return response with good status and headers
12
+ def save_and_respond(resource, options={}, &block)
13
+ creation = resource.new_record?
14
+ resource.attributes = options[:attributes] unless options[:attributes].nil?
15
+ respond_to do |format|
16
+ # if ((block_given? and block.arity == 1) ? yield(resource) : (block_given? and block.arity == 2) ? yield(resource, format) : resource.save)
17
+ if (block_given? ? yield(resource, format) : resource.save)
18
+ status = (creation ? :created : :ok)
19
+ response.headers["X-Return-Code"] = "success"
20
+ response.headers["X-Saved-Record-Id"] = resource.id.to_s
21
+ format.html { params[:dialog] ? head(status) : redirect_to(options[:url] || resource) }
22
+ format.xml { render :xml => resource, :status => status, :location => resource }
23
+ else
24
+ response.headers["X-Return-Code"] = "invalid"
25
+ format.html { render :action => (resource.new_record? ? "new" : "edit")}
26
+ format.xml { render :xml => resource.errors, :status => :unprocessable_entity }
27
+ end
28
+ end
29
+ end
30
+
31
+
8
32
 
9
33
  module ClassMethods
10
34
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.12
4
+ version: 0.0.13
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-12 00:00:00.000000000Z
12
+ date: 2011-09-14 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &8400960 !ruby/object:Gem::Requirement
16
+ requirement: &13456920 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *8400960
24
+ version_requirements: *13456920
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: jquery-rails
27
- requirement: &8376420 !ruby/object:Gem::Requirement
27
+ requirement: &13455880 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *8376420
35
+ version_requirements: *13455880
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: fastercsv
38
- requirement: &8373140 !ruby/object:Gem::Requirement
38
+ requirement: &13454620 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *8373140
46
+ version_requirements: *13454620
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: jeweler
49
- requirement: &8354560 !ruby/object:Gem::Requirement
49
+ requirement: &13453320 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.6.4
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *8354560
57
+ version_requirements: *13453320
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rcov
60
- requirement: &8349740 !ruby/object:Gem::Requirement
60
+ requirement: &13398260 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *8349740
68
+ version_requirements: *13398260
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rdoc
71
- requirement: &8346560 !ruby/object:Gem::Requirement
71
+ requirement: &13396340 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: 2.4.2
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *8346560
79
+ version_requirements: *13396340
80
80
  description: Like simple_form or formtastic, it aims to handle easily forms but taking
81
81
  in account AJAX and HTML5 on depending fields mainly.
82
82
  email: brice.texier@ekylibre.org