rails-backbone-forms 0.0.1

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.
@@ -0,0 +1,500 @@
1
+ ;(function() {
2
+
3
+ var Form = Backbone.Form,
4
+ Base = Form.editors.Base,
5
+ createTemplate = Form.helpers.createTemplate,
6
+ triggerCancellableEvent = Form.helpers.triggerCancellableEvent,
7
+ exports = {};
8
+
9
+ /**
10
+ * Additional editors that depend on jQuery UI
11
+ */
12
+
13
+ //DATE
14
+ exports['jqueryui.Date'] = Base.extend({
15
+
16
+ className: 'bbf-jui-date',
17
+
18
+ initialize: function(options) {
19
+ Base.prototype.initialize.call(this, options);
20
+
21
+ //Cast to Date
22
+ if (this.value && !_.isDate(this.value)) {
23
+ this.value = new Date(this.value);
24
+ }
25
+
26
+ //Set default date
27
+ if (!this.value) {
28
+ var date = new Date();
29
+ date.setSeconds(0);
30
+ date.setMilliseconds(0);
31
+
32
+ this.value = date;
33
+ }
34
+ },
35
+
36
+ render: function() {
37
+ var $el = this.$el;
38
+
39
+ $el.html('<input>');
40
+
41
+ var input = $('input', $el);
42
+
43
+ input.datepicker({
44
+ dateFormat: 'dd/mm/yy',
45
+ showButtonPanel: true
46
+ });
47
+
48
+ this._observeDatepickerEvents();
49
+
50
+ //Make sure setValue of this object is called, not of any objects extending it (e.g. DateTime)
51
+ exports['jqueryui.Date'].prototype.setValue.call(this, this.value);
52
+
53
+ return this;
54
+ },
55
+
56
+ /**
57
+ * @return {Date} Selected date
58
+ */
59
+ getValue: function() {
60
+ var input = $('input', this.el),
61
+ date = input.datepicker('getDate');
62
+
63
+ return date;
64
+ },
65
+
66
+ setValue: function(value) {
67
+ $('input', this.el).datepicker('setDate', value);
68
+ },
69
+
70
+ focus: function() {
71
+ if (this.hasFocus) return;
72
+
73
+ this.$('input').datepicker('show');
74
+ },
75
+
76
+ blur: function() {
77
+ if (!this.hasFocus) return;
78
+
79
+ this.$('input').datepicker('hide');
80
+ },
81
+
82
+ _observeDatepickerEvents: function() {
83
+ var self = this;
84
+ this.$('input').datepicker('option', 'onSelect', function() {
85
+ self.trigger('change', self);
86
+ })
87
+ this.$('input').datepicker('option', 'onClose', function() {
88
+ if (!self.hasFocus) return;
89
+ self.trigger('blur', self);
90
+ });
91
+ this.$('input').datepicker('option', 'beforeShow', function() {
92
+ if (self.hasFocus) return {};
93
+ self.trigger('focus', self);
94
+
95
+ return {};
96
+ });
97
+ }
98
+
99
+ });
100
+
101
+
102
+ //DATETIME
103
+ exports['jqueryui.DateTime'] = exports['jqueryui.Date'].extend({
104
+
105
+ className: 'bbf-jui-datetime',
106
+
107
+ template: createTemplate('<select>{{hours}}</select> : <select>{{mins}}</select>'),
108
+
109
+ render: function() {
110
+ function pad(n) {
111
+ return n < 10 ? '0' + n : n
112
+ }
113
+
114
+ //Render the date element first
115
+ exports['jqueryui.Date'].prototype.render.call(this);
116
+
117
+ //Setup hour options
118
+ var hours = _.range(0, 24),
119
+ hoursOptions = [];
120
+
121
+ _.each(hours, function(hour) {
122
+ hoursOptions.push('<option value="'+hour+'">' + pad(hour) + '</option>');
123
+ });
124
+
125
+ //Setup minute options
126
+ var minsInterval = this.schema.minsInterval || 15,
127
+ mins = _.range(0, 60, minsInterval),
128
+ minsOptions = [];
129
+
130
+ _.each(mins, function(min) {
131
+ minsOptions.push('<option value="'+min+'">' + pad(min) + '</option>');
132
+ });
133
+
134
+ //Render time selects
135
+ this.$el.append(this.template({
136
+ hours: hoursOptions.join(),
137
+ mins: minsOptions.join()
138
+ }));
139
+
140
+ this._observeDatepickerEvents();
141
+
142
+ //Store references to selects
143
+ this.$hours = $('select:eq(0)', this.el);
144
+ this.$mins = $('select:eq(1)', this.el);
145
+
146
+ //Set time
147
+ this.setValue(this.value);
148
+
149
+ return this;
150
+ },
151
+
152
+ /**
153
+ * @return {Date} Selected datetime
154
+ */
155
+ getValue: function() {
156
+ var input = $('input', this.el),
157
+ date = input.datepicker('getDate');
158
+
159
+ date.setHours(this.$hours.val());
160
+ date.setMinutes(this.$mins.val());
161
+ date.setMilliseconds(0);
162
+
163
+ return date;
164
+ },
165
+
166
+ setValue: function(date) {
167
+ exports['jqueryui.Date'].prototype.setValue.call(this, date);
168
+
169
+ this.$hours.val(date.getHours());
170
+ this.$mins.val(date.getMinutes());
171
+ }
172
+
173
+ });
174
+
175
+
176
+ //LIST
177
+ exports['jqueryui.List'] = Base.extend({
178
+
179
+ className: 'bbf-jui-list',
180
+
181
+ //Note: The extra div around the <ul> is used to limit the drag area
182
+ template: createTemplate('\
183
+ <ul></ul>\
184
+ <div><button class="bbf-list-add">Add</div>\
185
+ '),
186
+
187
+ itemTemplate: createTemplate('\
188
+ <li rel="{{id}}">\
189
+ <span class="bbf-list-text">{{text}}</span>\
190
+ <div class="bbf-list-actions">\
191
+ <button class="bbf-list-edit">Edit</button>\
192
+ <button class="bbf-list-del">Delete</button>\
193
+ </div>\
194
+ </li>\
195
+ '),
196
+
197
+ editorTemplate: createTemplate('\
198
+ <div class="bbf-field">\
199
+ <div class="bbf-list-editor"></div>\
200
+ </div>\
201
+ '),
202
+
203
+ events: {
204
+ 'click .bbf-list-add': 'addNewItem',
205
+ 'click .bbf-list-edit': 'editItem',
206
+ 'click .bbf-list-del': 'deleteItem'
207
+ },
208
+
209
+ initialize: function(options) {
210
+ Base.prototype.initialize.call(this, options);
211
+
212
+ if (!this.schema) throw "Missing required option 'schema'";
213
+
214
+ this.schema.listType = this.schema.listType || 'Text';
215
+
216
+ if (this.schema.listType == 'NestedModel' && !this.schema.model)
217
+ throw "Missing required option 'schema.model'";
218
+ },
219
+
220
+ render: function() {
221
+ var $el = this.$el;
222
+
223
+ //Main element
224
+ $el.html(this.template());
225
+
226
+ //Create list
227
+ var self = this,
228
+ data = this.value || [],
229
+ schema = this.schema,
230
+ itemToString = this.itemToString,
231
+ itemTemplate = this.itemTemplate,
232
+ listEl = $('ul', $el);
233
+
234
+ _.each(data, function(itemData) {
235
+ var text = itemToString.call(self, itemData);
236
+
237
+ //Create DOM element
238
+ var li = $(itemTemplate({
239
+ id: itemData.id || '',
240
+ text: text
241
+ }));
242
+
243
+ //Attach data
244
+ $.data(li[0], 'data', itemData);
245
+
246
+ listEl.append(li);
247
+ });
248
+
249
+ //Make sortable
250
+ if (schema.sortable !== false) {
251
+ listEl.sortable({
252
+ axis: 'y',
253
+ cursor: 'move',
254
+ containment: 'parent'
255
+ });
256
+
257
+ $el.addClass('bbf-list-sortable');
258
+ }
259
+
260
+ //jQuery UI buttonize
261
+ $('button.bbf-list-add', $el).button({
262
+ text: false,
263
+ icons: { primary: 'ui-icon-plus' }
264
+ });
265
+ $('button.bbf-list-edit', $el).button({
266
+ text: false,
267
+ icons: { primary: 'ui-icon-pencil' }
268
+ });
269
+ $('button.bbf-list-del', $el).button({
270
+ text: false,
271
+ icons: { primary: 'ui-icon-trash' }
272
+ });
273
+
274
+ if (this.hasFocus) this.trigger('blur', this);
275
+
276
+ return this;
277
+ },
278
+
279
+ /**
280
+ * Formats an item for display in the list
281
+ * For example objects, dates etc. can have a custom
282
+ * itemToString method which says how it should be formatted.
283
+ */
284
+ itemToString: function(data) {
285
+ if (!data) return data;
286
+
287
+ var schema = this.schema;
288
+
289
+ //If there's a specified toString use that
290
+ if (schema.itemToString) return schema.itemToString(data);
291
+
292
+ //Otherwise check if it's NestedModel with it's own toString() method
293
+ if (this.schema.listType == 'NestedModel') {
294
+ var model = new (this.schema.model)(data);
295
+
296
+ return model.toString();
297
+ }
298
+
299
+ //Last resort, just return the data as is
300
+ return data;
301
+ },
302
+
303
+ /**
304
+ * Add a new item to the list if it is completed in the editor
305
+ */
306
+ addNewItem: function(event) {
307
+ if (event) event.preventDefault();
308
+
309
+ var self = this;
310
+
311
+ this.openEditor(null, function(value, editor) {
312
+ //Fire 'addItem' cancellable event
313
+ triggerCancellableEvent(self, 'addItem', [value, editor], function() {
314
+ var text = self.itemToString(value);
315
+
316
+ //Create DOM element
317
+ var li = $(self.itemTemplate({
318
+ id: value.id || '',
319
+ text: text
320
+ }));
321
+
322
+ //Store data
323
+ $.data(li[0], 'data', value);
324
+
325
+ $('ul', self.el).append(li);
326
+
327
+ //jQuery UI buttonize
328
+ $('button.bbf-list-edit', this.el).button({
329
+ text: false,
330
+ icons: { primary: 'ui-icon-pencil' }
331
+ });
332
+ $('button.bbf-list-del', this.el).button({
333
+ text: false,
334
+ icons: { primary: 'ui-icon-trash' }
335
+ });
336
+
337
+ self.trigger('add', self, value);
338
+ self.trigger('item:change', self, editor);
339
+ self.trigger('change', self);
340
+ });
341
+ });
342
+ },
343
+
344
+ /**
345
+ * Edit an existing item in the list
346
+ */
347
+ editItem: function(event) {
348
+ event.preventDefault();
349
+
350
+ var self = this,
351
+ li = $(event.target).closest('li'),
352
+ originalValue = $.data(li[0], 'data');
353
+
354
+ this.openEditor(originalValue, function(newValue, editor) {
355
+ //Fire 'editItem' cancellable event
356
+ triggerCancellableEvent(self, 'editItem', [newValue, editor], function() {
357
+ //Update display
358
+ $('.bbf-list-text', li).html(self.itemToString(newValue));
359
+
360
+ //Store data
361
+ $.data(li[0], 'data', newValue);
362
+
363
+ self.trigger('item:change', self, editor);
364
+ self.trigger('change', self);
365
+ });
366
+ });
367
+ },
368
+
369
+ deleteItem: function(event) {
370
+ event.preventDefault();
371
+
372
+ var self = this,
373
+ li = $(event.target).closest('li'),
374
+ data = $.data(li[0], 'data');
375
+
376
+ var confirmDelete = (this.schema.confirmDelete) ? this.schema.confirmDelete : false,
377
+ confirmMsg = this.schema.confirmDeleteMsg || 'Are you sure?';
378
+
379
+ function remove() {
380
+ triggerCancellableEvent(self, 'removeItem', [data], function() {
381
+ li.remove();
382
+
383
+ self.trigger('remove', self, data);
384
+ self.trigger('change', self);
385
+ });
386
+ }
387
+
388
+ if (this.schema.confirmDelete) {
389
+ if (confirm(confirmMsg)) remove();
390
+ } else {
391
+ remove();
392
+ }
393
+ },
394
+
395
+ /**
396
+ * Opens the sub editor dialog
397
+ * @param {Mixed} Data (if editing existing list item, null otherwise)
398
+ * @param {Function} Save callback. receives: value
399
+ */
400
+ openEditor: function(data, callback) {
401
+ var self = this,
402
+ schema = this.schema,
403
+ listType = schema.listType || 'Text';
404
+
405
+ var editor = Form.helpers.createEditor(listType, {
406
+ key: '',
407
+ schema: schema,
408
+ value: data
409
+ }).render();
410
+
411
+ var container = this.editorContainer = $(this.editorTemplate());
412
+ $('.bbf-list-editor', container).html(editor.el);
413
+
414
+ var saveAndClose = function() {
415
+ var errs = editor.validate();
416
+ if (errs) return;
417
+
418
+ callback(editor.getValue(), editor);
419
+ container.dialog('close');
420
+ }
421
+
422
+ var handleEnterPressed = function(event) {
423
+ if (event.keyCode != 13) return;
424
+
425
+ saveAndClose();
426
+ }
427
+
428
+ $(container).dialog({
429
+ resizable: false,
430
+ modal: true,
431
+ width: 500,
432
+ title: data ? 'Edit item' : 'New item',
433
+ buttons: {
434
+ 'OK': saveAndClose,
435
+ 'Cancel': function() {
436
+ container.dialog('close');
437
+ }
438
+ },
439
+ close: function() {
440
+ self.editorContainer = null;
441
+
442
+ $(document).unbind('keydown', handleEnterPressed);
443
+
444
+ editor.remove();
445
+ container.remove();
446
+
447
+ self.trigger('item:close', self, editor);
448
+ self.trigger('item:blur', self, editor);
449
+ self.trigger('blur', self);
450
+ }
451
+ });
452
+
453
+ this.trigger('item:open', this, editor);
454
+ this.trigger('item:focus', this, editor);
455
+ this.trigger('focus', this);
456
+
457
+ //Save and close dialog on Enter keypress
458
+ $(document).bind('keydown', handleEnterPressed);
459
+ },
460
+
461
+ getValue: function() {
462
+ var data = [];
463
+
464
+ $('li', this.el).each(function(index, li) {
465
+ data.push($.data(li, 'data'));
466
+ });
467
+
468
+ return data;
469
+ },
470
+
471
+ setValue: function(value) {
472
+ this.value = value;
473
+ this.render();
474
+ },
475
+
476
+ focus: function() {
477
+ if (this.hasFocus) return;
478
+
479
+ var item = this.$('li .bbf-list-edit').first();
480
+ if (item.length > 0) {
481
+ item.click();
482
+ }
483
+ else {
484
+ this.addNewItem();
485
+ }
486
+ },
487
+
488
+ blur: function() {
489
+ if (!this.hasFocus) return;
490
+
491
+ if (this.editorContainer) this.editorContainer.dialog('close');
492
+ }
493
+
494
+ });
495
+
496
+
497
+ //Exports
498
+ _.extend(Form.editors, exports);
499
+
500
+ })();