rails-backbone-forms 0.0.1

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