backbone-forms-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,405 @@
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
+ exports.Date = Base.extend({
13
+
14
+ className: 'bbf-date',
15
+
16
+ initialize: function(options) {
17
+ Base.prototype.initialize.call(this, options);
18
+
19
+ if (!this.value) {
20
+ var date = new Date();
21
+ date.setSeconds(0);
22
+ date.setMilliseconds(0);
23
+
24
+ this.value = date;
25
+ }
26
+ },
27
+
28
+ render: function() {
29
+ var el = $(this.el);
30
+
31
+ el.html('<input>');
32
+
33
+ var input = $('input', el);
34
+
35
+ input.datepicker({
36
+ dateFormat: 'dd/mm/yy',
37
+ showButtonPanel: true
38
+ });
39
+
40
+ //Make sure setValue of this object is called, not of any objects extending it (e.g. DateTime)
41
+ exports.Date.prototype.setValue.call(this, this.value);
42
+
43
+ return this;
44
+ },
45
+
46
+ /**
47
+ * @return {Date} Selected date
48
+ */
49
+ getValue: function() {
50
+ var input = $('input', this.el),
51
+ date = input.datepicker('getDate');
52
+
53
+ return date;
54
+ },
55
+
56
+ setValue: function(value) {
57
+ $('input', this.el).datepicker('setDate', value);
58
+ }
59
+
60
+ });
61
+
62
+
63
+
64
+ exports.DateTime = exports.Date.extend({
65
+
66
+ className: 'bbf-datetime',
67
+
68
+ template: createTemplate('<select>{{hours}}</select> : <select>{{mins}}</select>'),
69
+
70
+ render: function() {
71
+ function pad(n) {
72
+ return n < 10 ? '0' + n : n
73
+ }
74
+
75
+ //Render the date element first
76
+ exports.Date.prototype.render.call(this);
77
+
78
+ //Setup hour options
79
+ var hours = _.range(0, 24),
80
+ hoursOptions = [];
81
+
82
+ _.each(hours, function(hour) {
83
+ hoursOptions.push('<option value="'+hour+'">' + pad(hour) + '</option>');
84
+ });
85
+
86
+ //Setup minute options
87
+ var minsInterval = this.schema.minsInterval || 15,
88
+ mins = _.range(0, 60, minsInterval),
89
+ minsOptions = [];
90
+
91
+ _.each(mins, function(min) {
92
+ minsOptions.push('<option value="'+min+'">' + pad(min) + '</option>');
93
+ });
94
+
95
+ //Render time selects
96
+ $(this.el).append(this.template({
97
+ hours: hoursOptions.join(),
98
+ mins: minsOptions.join()
99
+ }));
100
+
101
+ //Store references to selects
102
+ this.$hours = $('select:eq(0)', this.el);
103
+ this.$mins = $('select:eq(1)', this.el);
104
+
105
+ //Set time
106
+ this.setValue(this.value);
107
+
108
+ return this;
109
+ },
110
+
111
+ /**
112
+ * @return {Date} Selected datetime
113
+ */
114
+ getValue: function() {
115
+ var input = $('input', this.el),
116
+ date = input.datepicker('getDate');
117
+
118
+ date.setHours(this.$hours.val());
119
+ date.setMinutes(this.$mins.val());
120
+ date.setMilliseconds(0);
121
+
122
+ return date;
123
+ },
124
+
125
+ setValue: function(date) {
126
+ exports.Date.prototype.setValue.call(this, date);
127
+
128
+ this.$hours.val(date.getHours());
129
+ this.$mins.val(date.getMinutes());
130
+ }
131
+
132
+ });
133
+
134
+
135
+ exports.List = Base.extend({
136
+
137
+ className: 'bbf-list',
138
+
139
+ //Note: The extra div around the <ul> is used to limit the drag area
140
+ template: createTemplate('\
141
+ <ul></ul>\
142
+ <div><button class="bbf-list-add">Add</div>\
143
+ '),
144
+
145
+ itemTemplate: createTemplate('\
146
+ <li rel="{{id}}">\
147
+ <span class="bbf-list-text">{{text}}</span>\
148
+ <div class="bbf-list-actions">\
149
+ <button class="bbf-list-edit">Edit</button>\
150
+ <button class="bbf-list-del">Delete</button>\
151
+ </div>\
152
+ </li>\
153
+ '),
154
+
155
+ editorTemplate: createTemplate('\
156
+ <div class="bbf-field">\
157
+ <div class="bbf-list-editor"></div>\
158
+ </div>\
159
+ '),
160
+
161
+ events: {
162
+ 'click .bbf-list-add': 'addNewItem',
163
+ 'click .bbf-list-edit': 'editItem',
164
+ 'click .bbf-list-del': 'deleteItem'
165
+ },
166
+
167
+ initialize: function(options) {
168
+ Base.prototype.initialize.call(this, options);
169
+
170
+ if (!this.schema) throw "Missing required option 'schema'";
171
+
172
+ this.schema.listType = this.schema.listType || 'Text';
173
+
174
+ if (this.schema.listType == 'NestedModel' && !this.schema.model)
175
+ throw "Missing required option 'schema.model'";
176
+ },
177
+
178
+ render: function() {
179
+ var el = $(this.el);
180
+
181
+ //Main element
182
+ el.html(this.template());
183
+
184
+ //Create list
185
+ var self = this,
186
+ el = $(this.el),
187
+ data = this.value || [],
188
+ schema = this.schema,
189
+ itemToString = this.itemToString,
190
+ itemTemplate = this.itemTemplate,
191
+ listEl = $('ul', el);
192
+
193
+ _.each(data, function(itemData) {
194
+ var text = itemToString.call(self, itemData);
195
+
196
+ //Create DOM element
197
+ var li = $(itemTemplate({
198
+ id: itemData.id || '',
199
+ text: text
200
+ }));
201
+
202
+ //Attach data
203
+ $.data(li[0], 'data', itemData);
204
+
205
+ listEl.append(li);
206
+ });
207
+
208
+ //Make sortable
209
+ if (schema.sortable !== false) {
210
+ listEl.sortable({
211
+ axis: 'y',
212
+ cursor: 'move',
213
+ containment: 'parent'
214
+ });
215
+
216
+ el.addClass('bbf-list-sortable');
217
+ }
218
+
219
+ //jQuery UI buttonize
220
+ $('button.bbf-list-add', el).button({
221
+ text: false,
222
+ icons: { primary: 'ui-icon-plus' }
223
+ });
224
+ $('button.bbf-list-edit', el).button({
225
+ text: false,
226
+ icons: { primary: 'ui-icon-pencil' }
227
+ });
228
+ $('button.bbf-list-del', el).button({
229
+ text: false,
230
+ icons: { primary: 'ui-icon-trash' }
231
+ });
232
+
233
+ return this;
234
+ },
235
+
236
+ /**
237
+ * Formats an item for display in the list
238
+ * For example objects, dates etc. can have a custom
239
+ * itemToString method which says how it should be formatted.
240
+ */
241
+ itemToString: function(data) {
242
+ if (!data) return data;
243
+
244
+ var schema = this.schema;
245
+
246
+ //If there's a specified toString use that
247
+ if (schema.itemToString)
248
+ return schema.itemToString(data);
249
+
250
+ //Otherwise check if it's NestedModel with it's own toString() method
251
+ if (this.schema.listType == 'NestedModel') {
252
+ var model = new (this.schema.model)(data);
253
+
254
+ return model.toString();
255
+ }
256
+
257
+ //Last resort, just return the data as is
258
+ return data;
259
+ },
260
+
261
+ /**
262
+ * Add a new item to the list if it is completed in the editor
263
+ */
264
+ addNewItem: function(event) {
265
+ event.preventDefault();
266
+
267
+ var self = this;
268
+
269
+ this.openEditor(null, function(value) {
270
+ //Fire 'addItem' cancellable event
271
+ triggerCancellableEvent(self, 'addItem', [value], function() {
272
+ var text = self.itemToString(value);
273
+
274
+ //Create DOM element
275
+ var li = $(self.itemTemplate({
276
+ id: value.id || '',
277
+ text: text
278
+ }));
279
+
280
+ //Store data
281
+ $.data(li[0], 'data', value);
282
+
283
+ $('ul', self.el).append(li);
284
+
285
+ //jQuery UI buttonize
286
+ $('button.bbf-list-edit', this.el).button({
287
+ text: false,
288
+ icons: { primary: 'ui-icon-pencil' }
289
+ });
290
+ $('button.bbf-list-del', this.el).button({
291
+ text: false,
292
+ icons: { primary: 'ui-icon-trash' }
293
+ });
294
+ });
295
+ });
296
+ },
297
+
298
+ /**
299
+ * Edit an existing item in the list
300
+ */
301
+ editItem: function(event) {
302
+ event.preventDefault();
303
+
304
+ var self = this,
305
+ li = $(event.target).closest('li'),
306
+ originalValue = $.data(li[0], 'data');
307
+
308
+ this.openEditor(originalValue, function(newValue) {
309
+ //Fire 'editItem' cancellable event
310
+ triggerCancellableEvent(self, 'editItem', [newValue], function() {
311
+ //Update display
312
+ $('.bbf-list-text', li).html(self.itemToString(newValue));
313
+
314
+ //Store data
315
+ $.data(li[0], 'data', newValue);
316
+ });
317
+ });
318
+ },
319
+
320
+ deleteItem: function(event) {
321
+ event.preventDefault();
322
+
323
+ var self = this,
324
+ li = $(event.target).closest('li'),
325
+ data = $.data(li[0], 'data');
326
+
327
+ var confirmDelete = (this.schema.confirmDelete) ? this.schema.confirmDelete : false,
328
+ confirmMsg = this.schema.confirmDeleteMsg || 'Are you sure?';
329
+
330
+ function remove() {
331
+ triggerCancellableEvent(self, 'removeItem', [data], function() {
332
+ li.remove();
333
+ });
334
+ }
335
+
336
+ if (this.schema.confirmDelete) {
337
+ if (confirm(confirmMsg)) remove();
338
+ } else {
339
+ remove();
340
+ }
341
+ },
342
+
343
+ /**
344
+ * Opens the sub editor dialog
345
+ * @param {Mixed} Data (if editing existing list item, null otherwise)
346
+ * @param {Function} Save callback. receives: value
347
+ */
348
+ openEditor: function(data, callback) {
349
+ var self = this,
350
+ schema = this.schema,
351
+ listType = schema.listType || 'Text';
352
+
353
+ var editor = Form.helpers.createEditor(listType, {
354
+ key: '',
355
+ schema: schema,
356
+ value: data
357
+ }).render();
358
+
359
+ var container = $(this.editorTemplate());
360
+ $('.bbf-list-editor', container).html(editor.el);
361
+
362
+ var close = function() {
363
+ container.dialog('close');
364
+
365
+ editor.remove();
366
+ container.remove();
367
+ };
368
+
369
+ $(container).dialog({
370
+ resizable: false,
371
+ modal: true,
372
+ width: 500,
373
+ title: data ? 'Edit item' : 'New item',
374
+ buttons: {
375
+ 'OK': function() {
376
+ callback(editor.getValue());
377
+ close();
378
+ },
379
+ 'Cancel': close
380
+ }
381
+ });
382
+ },
383
+
384
+ getValue: function() {
385
+ var data = [];
386
+
387
+ $('li', this.el).each(function(index, li) {
388
+ data.push($.data(li, 'data'));
389
+ });
390
+
391
+ return data;
392
+ },
393
+
394
+ setValue: function(value) {
395
+ this.value = value;
396
+ this.render();
397
+ }
398
+
399
+ });
400
+
401
+
402
+ //Exports
403
+ _.extend(Form.editors, exports);
404
+
405
+ })();
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require backbone-forms-rails/backbone-forms.css
3
+ */
@@ -0,0 +1,97 @@
1
+ .bbf-form {
2
+ margin: 0;
3
+ padding: 0;
4
+ }
5
+
6
+
7
+ .bbf-field {
8
+ margin: 1em 0;
9
+ list-style-type: none;
10
+ }
11
+
12
+ .bbf-field label {
13
+ float: left;
14
+ width: 25%;
15
+ }
16
+
17
+ .bbf-field .bbf-editor {
18
+ margin-left: 25%;
19
+ width: 74%;
20
+ }
21
+
22
+ .bbf-field input, .bbf-field textarea, .bbf-field select {
23
+ width: 100%;
24
+ }
25
+
26
+ .bbf-field .bbf-error {
27
+ outline: 1px solid red;
28
+ }
29
+
30
+
31
+
32
+ .bbf-date input, .bbf-datetime input {
33
+ width: 100px;
34
+ text-align: center;
35
+ }
36
+
37
+ .bbf-datetime input {
38
+ margin-right: 1em;
39
+ }
40
+
41
+ .bbf-datetime select {
42
+ width: 50px;
43
+ text-align: center;
44
+ }
45
+
46
+
47
+
48
+ .bbf-list {
49
+ list-style-type: none;
50
+ }
51
+
52
+ .bbf-list ul {
53
+ border: 1px solid #ccc;
54
+ border-bottom: none;
55
+ max-height: 150px;
56
+ overflow: auto;
57
+ margin: 0;
58
+ padding: 0;
59
+ }
60
+
61
+ .bbf-list li {
62
+ border-top: 1px solid #ccc;
63
+ border-bottom: 1px solid #ccc;
64
+ height: 16px;
65
+ background: #fff;
66
+ padding: 4px;
67
+ margin: 0;
68
+ list-style-type: none;
69
+ margin-top: -1px;
70
+ position: relative;
71
+
72
+ }
73
+ .bbf-list-sortable li {
74
+ cursor: move;
75
+ }
76
+
77
+ .bbf-list .bbf-list-actions {
78
+ position: absolute;
79
+ top: 2px;
80
+ right: 2px;
81
+ }
82
+
83
+ .bbf-list .bbf-list-actions button {
84
+ width: 19px;
85
+ height: 19px;
86
+ }
87
+
88
+ .bbf-list .bbf-list-add {
89
+ width: 100%;
90
+ height: 20px;
91
+ margin-top: -2px;
92
+ }
93
+
94
+ .bbf-list-editor {
95
+ width: 98%;
96
+ }
97
+