backbone_form_helper 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in backbone_form_helper.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Zdenal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # BackboneFormHelper
2
+
3
+ This gem provide form helper based on Rails principle like 'f.text_file
4
+ :name'. For each helper exist template so you can create your own
5
+ template for each of them (eg. /templates/text_field). Now are templates
6
+ created in Twitter bootstrap style.
7
+
8
+ **This is still alpha.**
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'backbone_form_helper'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install backbone_form_helper
23
+
24
+ ## Helper list
25
+ For now:
26
+
27
+ * label
28
+ * text_field
29
+ * text_area
30
+ * select
31
+ * select_tag
32
+ * check_box
33
+ * date_field
34
+
35
+ ## Usage
36
+
37
+ <% form = new FormHelper @task %>
38
+ <%- form.check_box 'is_done', title: 'Is done' %>
39
+ <%- form.date_field 'date', class: 'text_field', placeholder: 'datum' %>
40
+ <%- form.text_area 'description', placeholder: 'ukol' %>
41
+ <%- form.select 'group_id', values: _.map(@groups, (g) -> [g.get('_id'), g.get('name')]) %>
42
+ <%- form.select 'user_id', values: _.map(@users, (g) -> [g.get('_id'), g.get('name')]) %>
43
+
44
+
45
+ Value and errors are taken from model automaticly how Rails do it and shown in template (eg. text_field template):
46
+
47
+ <input type="text" id="<%= @field_id %>" <%- @unfold_options %> name="<%= @field_name %>" value="<%= @value %>" />
48
+ <span class="help-inline">
49
+ <%= @errors if @errors %>
50
+ </span>
51
+
52
+ so after render when model has error on name (model.errors['name'] or model.get('errors')['name']) it looks like:
53
+
54
+ <input type="text" id="task_name" placeholder="ukol" class="error" name="task[name]" value="">
55
+ <span class="help-inline">
56
+ can't be blank
57
+ </span>
58
+
59
+ ## Contributing
60
+
61
+ 1. Fork it
62
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
63
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
64
+ 4. Push to the branch (`git push origin my-new-feature`)
65
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/backbone_form_helper/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Zdenal"]
6
+ gem.email = ["nevralaz@gmail.com"]
7
+ gem.description = %q{Form helper for backbone based on Rails priciple -> f.text_field :name}
8
+ gem.summary = %q{Backbone form helper}
9
+ gem.homepage = "http://github.com/zdenal/backbone_form_helper.git"
10
+ gem.rubyforge_project = "backbone_form_helper"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "backbone_form_helper"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = BackboneFormHelper::VERSION
18
+ end
@@ -0,0 +1,6 @@
1
+ module BackboneFormHelper
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module BackboneFormHelper
2
+ VERSION = "0.0.1.alpha"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "backbone_form_helper/version"
2
+ require "backbone_form_helper/engine"
3
+
4
+ module BackboneFormHelper
5
+ end
@@ -0,0 +1,818 @@
1
+ /* =========================================================
2
+ * bootstrap-datepicker.js
3
+ * http://www.eyecon.ro/bootstrap-datepicker
4
+ * =========================================================
5
+ * Copyright 2012 Stefan Petre
6
+ * Improvements by Andrew Rowls
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ * ========================================================= */
20
+
21
+ !function( $ ) {
22
+
23
+ function UTCDate(){
24
+ return new Date(Date.UTC.apply(Date, arguments));
25
+ }
26
+ function UTCToday(){
27
+ var today = new Date();
28
+ return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
29
+ }
30
+
31
+ // Picker object
32
+
33
+ var Datepicker = function(element, options) {
34
+ var that = this;
35
+
36
+ this.element = $(element);
37
+ this.language = options.language||this.element.data('date-language')||"en";
38
+ this.language = this.language in dates ? this.language : "en";
39
+ this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
40
+ this.picker = $(DPGlobal.template)
41
+ .appendTo('body')
42
+ .on({
43
+ click: $.proxy(this.click, this)
44
+ });
45
+ this.isInput = this.element.is('input');
46
+ this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
47
+ this.hasInput = this.component && this.element.find('input').length;
48
+ if(this.component && this.component.length === 0)
49
+ this.component = false;
50
+
51
+ if (this.isInput) {
52
+ this.element.on({
53
+ focus: $.proxy(this.show, this),
54
+ keyup: $.proxy(this.update, this),
55
+ keydown: $.proxy(this.keydown, this)
56
+ });
57
+ } else {
58
+ if (this.component && this.hasInput){
59
+ // For components that are not readonly, allow keyboard nav
60
+ this.element.find('input').on({
61
+ focus: $.proxy(this.show, this),
62
+ keyup: $.proxy(this.update, this),
63
+ keydown: $.proxy(this.keydown, this)
64
+ });
65
+
66
+ this.component.on('click', $.proxy(this.show, this));
67
+ } else {
68
+ this.element.on('click', $.proxy(this.show, this));
69
+ }
70
+ }
71
+
72
+ $(document).on('mousedown', function (e) {
73
+ // Clicked outside the datepicker, hide it
74
+ if ($(e.target).closest('.datepicker').length == 0) {
75
+ that.hide();
76
+ }
77
+ });
78
+
79
+ this.autoclose = false;
80
+ if ('autoclose' in options) {
81
+ this.autoclose = options.autoclose;
82
+ } else if ('dateAutoclose' in this.element.data()) {
83
+ this.autoclose = this.element.data('date-autoclose');
84
+ }
85
+
86
+ this.keyboardNavigation = true;
87
+ if ('keyboardNavigation' in options) {
88
+ this.keyboardNavigation = options.keyboardNavigation;
89
+ } else if ('dateKeyboardNavigation' in this.element.data()) {
90
+ this.keyboardNavigation = this.element.data('date-keyboard-navigation');
91
+ }
92
+
93
+ switch(options.startView || this.element.data('date-start-view')){
94
+ case 2:
95
+ case 'decade':
96
+ this.viewMode = this.startViewMode = 2;
97
+ break;
98
+ case 1:
99
+ case 'year':
100
+ this.viewMode = this.startViewMode = 1;
101
+ break;
102
+ case 0:
103
+ case 'month':
104
+ default:
105
+ this.viewMode = this.startViewMode = 0;
106
+ break;
107
+ }
108
+
109
+ this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
110
+ this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
111
+
112
+ this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
113
+ this.weekEnd = ((this.weekStart + 6) % 7);
114
+ this.startDate = -Infinity;
115
+ this.endDate = Infinity;
116
+ this.setStartDate(options.startDate||this.element.data('date-startdate'));
117
+ this.setEndDate(options.endDate||this.element.data('date-enddate'));
118
+ this.fillDow();
119
+ this.fillMonths();
120
+ this.update();
121
+ this.showMode();
122
+ };
123
+
124
+ Datepicker.prototype = {
125
+ constructor: Datepicker,
126
+
127
+ show: function(e) {
128
+ this.picker.show();
129
+ this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
130
+ this.update();
131
+ this.place();
132
+ $(window).on('resize', $.proxy(this.place, this));
133
+ if (e ) {
134
+ e.stopPropagation();
135
+ e.preventDefault();
136
+ }
137
+ this.element.trigger({
138
+ type: 'show',
139
+ date: this.date
140
+ });
141
+ },
142
+
143
+ hide: function(e){
144
+ this.picker.hide();
145
+ $(window).off('resize', this.place);
146
+ this.viewMode = this.startViewMode;
147
+ this.showMode();
148
+ if (!this.isInput) {
149
+ $(document).off('mousedown', this.hide);
150
+ }
151
+ if (e && e.currentTarget.value)
152
+ this.setValue();
153
+ this.element.trigger({
154
+ type: 'hide',
155
+ date: this.date
156
+ });
157
+ },
158
+
159
+ setValue: function() {
160
+ var formatted = DPGlobal.formatDate(this.date, this.format, this.language);
161
+ if (!this.isInput) {
162
+ if (this.component){
163
+ this.element.find('input').prop('value', formatted);
164
+ }
165
+ this.element.data('date', formatted);
166
+ } else {
167
+ this.element.prop('value', formatted);
168
+ }
169
+ },
170
+
171
+ setStartDate: function(startDate){
172
+ this.startDate = startDate||-Infinity;
173
+ if (this.startDate !== -Infinity) {
174
+ this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
175
+ }
176
+ this.update();
177
+ this.updateNavArrows();
178
+ },
179
+
180
+ setEndDate: function(endDate){
181
+ this.endDate = endDate||Infinity;
182
+ if (this.endDate !== Infinity) {
183
+ this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
184
+ }
185
+ this.update();
186
+ this.updateNavArrows();
187
+ },
188
+
189
+ place: function(){
190
+ var zIndex = parseInt(this.element.parents().filter(function() {
191
+ return $(this).css('z-index') != 'auto';
192
+ }).first().css('z-index'))+10;
193
+ var offset = this.component ? this.component.offset() : this.element.offset();
194
+ this.picker.css({
195
+ top: offset.top + this.height,
196
+ left: offset.left,
197
+ zIndex: zIndex
198
+ });
199
+ },
200
+
201
+ update: function(){
202
+ this.date = DPGlobal.parseDate(
203
+ this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value'),
204
+ this.format, this.language
205
+ );
206
+ if (this.date < this.startDate) {
207
+ this.viewDate = new Date(this.startDate);
208
+ } else if (this.date > this.endDate) {
209
+ this.viewDate = new Date(this.endDate);
210
+ } else {
211
+ this.viewDate = new Date(this.date);
212
+ }
213
+ this.fill();
214
+ },
215
+
216
+ fillDow: function(){
217
+ var dowCnt = this.weekStart;
218
+ var html = '<tr>';
219
+ while (dowCnt < this.weekStart + 7) {
220
+ html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
221
+ }
222
+ html += '</tr>';
223
+ this.picker.find('.datepicker-days thead').append(html);
224
+ },
225
+
226
+ fillMonths: function(){
227
+ var html = '';
228
+ var i = 0
229
+ while (i < 12) {
230
+ html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
231
+ }
232
+ this.picker.find('.datepicker-months td').html(html);
233
+ },
234
+
235
+ fill: function() {
236
+ var d = new Date(this.viewDate),
237
+ year = d.getUTCFullYear(),
238
+ month = d.getUTCMonth(),
239
+ startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
240
+ startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
241
+ endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
242
+ endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
243
+ currentDate = this.date.valueOf(),
244
+ today = new Date();
245
+ this.picker.find('.datepicker-days thead th:eq(1)')
246
+ .text(dates[this.language].months[month]+' '+year);
247
+ this.picker.find('tfoot th.today')
248
+ .text(dates[this.language].today)
249
+ .toggle(this.todayBtn);
250
+ this.updateNavArrows();
251
+ this.fillMonths();
252
+ var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
253
+ day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
254
+ prevMonth.setUTCDate(day);
255
+ prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
256
+ var nextMonth = new Date(prevMonth);
257
+ nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
258
+ nextMonth = nextMonth.valueOf();
259
+ var html = [];
260
+ var clsName;
261
+ while(prevMonth.valueOf() < nextMonth) {
262
+ if (prevMonth.getUTCDay() == this.weekStart) {
263
+ html.push('<tr>');
264
+ }
265
+ clsName = '';
266
+ if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
267
+ clsName += ' old';
268
+ } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
269
+ clsName += ' new';
270
+ }
271
+ // Compare internal UTC date with local today, not UTC today
272
+ if (this.todayHighlight &&
273
+ prevMonth.getUTCFullYear() == today.getFullYear() &&
274
+ prevMonth.getUTCMonth() == today.getMonth() &&
275
+ prevMonth.getUTCDate() == today.getDate()) {
276
+ clsName += ' today';
277
+ }
278
+ if (prevMonth.valueOf() == currentDate) {
279
+ clsName += ' active';
280
+ }
281
+ if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
282
+ clsName += ' disabled';
283
+ }
284
+ html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
285
+ if (prevMonth.getUTCDay() == this.weekEnd) {
286
+ html.push('</tr>');
287
+ }
288
+ prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
289
+ }
290
+ this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
291
+ var currentYear = this.date.getUTCFullYear();
292
+
293
+ var months = this.picker.find('.datepicker-months')
294
+ .find('th:eq(1)')
295
+ .text(year)
296
+ .end()
297
+ .find('span').removeClass('active');
298
+ if (currentYear == year) {
299
+ months.eq(this.date.getUTCMonth()).addClass('active');
300
+ }
301
+ if (year < startYear || year > endYear) {
302
+ months.addClass('disabled');
303
+ }
304
+ if (year == startYear) {
305
+ months.slice(0, startMonth).addClass('disabled');
306
+ }
307
+ if (year == endYear) {
308
+ months.slice(endMonth+1).addClass('disabled');
309
+ }
310
+
311
+ html = '';
312
+ year = parseInt(year/10, 10) * 10;
313
+ var yearCont = this.picker.find('.datepicker-years')
314
+ .find('th:eq(1)')
315
+ .text(year + '-' + (year + 9))
316
+ .end()
317
+ .find('td');
318
+ year -= 1;
319
+ for (var i = -1; i < 11; i++) {
320
+ html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
321
+ year += 1;
322
+ }
323
+ yearCont.html(html);
324
+ },
325
+
326
+ updateNavArrows: function() {
327
+ var d = new Date(this.viewDate),
328
+ year = d.getUTCFullYear(),
329
+ month = d.getUTCMonth();
330
+ switch (this.viewMode) {
331
+ case 0:
332
+ if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
333
+ this.picker.find('.prev').css({visibility: 'hidden'});
334
+ } else {
335
+ this.picker.find('.prev').css({visibility: 'visible'});
336
+ }
337
+ if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
338
+ this.picker.find('.next').css({visibility: 'hidden'});
339
+ } else {
340
+ this.picker.find('.next').css({visibility: 'visible'});
341
+ }
342
+ break;
343
+ case 1:
344
+ case 2:
345
+ if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
346
+ this.picker.find('.prev').css({visibility: 'hidden'});
347
+ } else {
348
+ this.picker.find('.prev').css({visibility: 'visible'});
349
+ }
350
+ if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
351
+ this.picker.find('.next').css({visibility: 'hidden'});
352
+ } else {
353
+ this.picker.find('.next').css({visibility: 'visible'});
354
+ }
355
+ break;
356
+ }
357
+ },
358
+
359
+ click: function(e) {
360
+ e.stopPropagation();
361
+ e.preventDefault();
362
+ var target = $(e.target).closest('span, td, th');
363
+ if (target.length == 1) {
364
+ switch(target[0].nodeName.toLowerCase()) {
365
+ case 'th':
366
+ switch(target[0].className) {
367
+ case 'switch':
368
+ this.showMode(1);
369
+ break;
370
+ case 'prev':
371
+ case 'next':
372
+ var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
373
+ switch(this.viewMode){
374
+ case 0:
375
+ this.viewDate = this.moveMonth(this.viewDate, dir);
376
+ break;
377
+ case 1:
378
+ case 2:
379
+ this.viewDate = this.moveYear(this.viewDate, dir);
380
+ break;
381
+ }
382
+ this.fill();
383
+ break;
384
+ case 'today':
385
+ var date = new Date();
386
+ date.setUTCHours(0);
387
+ date.setUTCMinutes(0);
388
+ date.setUTCSeconds(0);
389
+ date.setUTCMilliseconds(0);
390
+
391
+ this.showMode(-2);
392
+ var which = this.todayBtn == 'linked' ? null : 'view';
393
+ this._setDate(date, which);
394
+ break;
395
+ }
396
+ break;
397
+ case 'span':
398
+ if (!target.is('.disabled')) {
399
+ this.viewDate.setUTCDate(1);
400
+ if (target.is('.month')) {
401
+ var month = target.parent().find('span').index(target);
402
+ this.viewDate.setUTCMonth(month);
403
+ this.element.trigger({
404
+ type: 'changeMonth',
405
+ date: this.viewDate
406
+ });
407
+ } else {
408
+ var year = parseInt(target.text(), 10)||0;
409
+ this.viewDate.setUTCFullYear(year);
410
+ this.element.trigger({
411
+ type: 'changeYear',
412
+ date: this.viewDate
413
+ });
414
+ }
415
+ this.showMode(-1);
416
+ this.fill();
417
+ }
418
+ break;
419
+ case 'td':
420
+ if (target.is('.day') && !target.is('.disabled')){
421
+ var day = parseInt(target.text(), 10)||1;
422
+ var year = this.viewDate.getUTCFullYear(),
423
+ month = this.viewDate.getUTCMonth();
424
+ if (target.is('.old')) {
425
+ if (month == 0) {
426
+ month = 11;
427
+ year -= 1;
428
+ } else {
429
+ month -= 1;
430
+ }
431
+ } else if (target.is('.new')) {
432
+ if (month == 11) {
433
+ month = 0;
434
+ year += 1;
435
+ } else {
436
+ month += 1;
437
+ }
438
+ }
439
+ this._setDate(UTCDate(year, month, day,0,0,0,0));
440
+ }
441
+ break;
442
+ }
443
+ }
444
+ },
445
+
446
+ _setDate: function(date, which){
447
+ if (!which || which == 'date')
448
+ this.date = date;
449
+ if (!which || which == 'view')
450
+ this.viewDate = date;
451
+ this.fill();
452
+ this.setValue();
453
+ this.element.trigger({
454
+ type: 'changeDate',
455
+ date: this.date
456
+ });
457
+ var element;
458
+ if (this.isInput) {
459
+ element = this.element;
460
+ } else if (this.component){
461
+ element = this.element.find('input');
462
+ }
463
+ if (element) {
464
+ element.change();
465
+ if (this.autoclose) {
466
+ this.hide();
467
+ }
468
+ }
469
+ },
470
+
471
+ moveMonth: function(date, dir){
472
+ if (!dir) return date;
473
+ var new_date = new Date(date.valueOf()),
474
+ day = new_date.getUTCDate(),
475
+ month = new_date.getUTCMonth(),
476
+ mag = Math.abs(dir),
477
+ new_month, test;
478
+ dir = dir > 0 ? 1 : -1;
479
+ if (mag == 1){
480
+ test = dir == -1
481
+ // If going back one month, make sure month is not current month
482
+ // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
483
+ ? function(){ return new_date.getUTCMonth() == month; }
484
+ // If going forward one month, make sure month is as expected
485
+ // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
486
+ : function(){ return new_date.getUTCMonth() != new_month; };
487
+ new_month = month + dir;
488
+ new_date.setUTCMonth(new_month);
489
+ // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
490
+ if (new_month < 0 || new_month > 11)
491
+ new_month = (new_month + 12) % 12;
492
+ } else {
493
+ // For magnitudes >1, move one month at a time...
494
+ for (var i=0; i<mag; i++)
495
+ // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
496
+ new_date = this.moveMonth(new_date, dir);
497
+ // ...then reset the day, keeping it in the new month
498
+ new_month = new_date.getUTCMonth();
499
+ new_date.setUTCDate(day);
500
+ test = function(){ return new_month != new_date.getUTCMonth(); };
501
+ }
502
+ // Common date-resetting loop -- if date is beyond end of month, make it
503
+ // end of month
504
+ while (test()){
505
+ new_date.setUTCDate(--day);
506
+ new_date.setUTCMonth(new_month);
507
+ }
508
+ return new_date;
509
+ },
510
+
511
+ moveYear: function(date, dir){
512
+ return this.moveMonth(date, dir*12);
513
+ },
514
+
515
+ dateWithinRange: function(date){
516
+ return date >= this.startDate && date <= this.endDate;
517
+ },
518
+
519
+ keydown: function(e){
520
+ if (this.picker.is(':not(:visible)')){
521
+ if (e.keyCode == 27) // allow escape to hide and re-show picker
522
+ this.show();
523
+ return;
524
+ }
525
+ var dateChanged = false,
526
+ dir, day, month,
527
+ newDate, newViewDate;
528
+ switch(e.keyCode){
529
+ case 27: // escape
530
+ this.hide();
531
+ e.preventDefault();
532
+ break;
533
+ case 37: // left
534
+ case 39: // right
535
+ if (!this.keyboardNavigation) break;
536
+ dir = e.keyCode == 37 ? -1 : 1;
537
+ if (e.ctrlKey){
538
+ newDate = this.moveYear(this.date, dir);
539
+ newViewDate = this.moveYear(this.viewDate, dir);
540
+ } else if (e.shiftKey){
541
+ newDate = this.moveMonth(this.date, dir);
542
+ newViewDate = this.moveMonth(this.viewDate, dir);
543
+ } else {
544
+ newDate = new Date(this.date);
545
+ newDate.setUTCDate(this.date.getUTCDate() + dir);
546
+ newViewDate = new Date(this.viewDate);
547
+ newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
548
+ }
549
+ if (this.dateWithinRange(newDate)){
550
+ this.date = newDate;
551
+ this.viewDate = newViewDate;
552
+ this.setValue();
553
+ this.update();
554
+ e.preventDefault();
555
+ dateChanged = true;
556
+ }
557
+ break;
558
+ case 38: // up
559
+ case 40: // down
560
+ if (!this.keyboardNavigation) break;
561
+ dir = e.keyCode == 38 ? -1 : 1;
562
+ if (e.ctrlKey){
563
+ newDate = this.moveYear(this.date, dir);
564
+ newViewDate = this.moveYear(this.viewDate, dir);
565
+ } else if (e.shiftKey){
566
+ newDate = this.moveMonth(this.date, dir);
567
+ newViewDate = this.moveMonth(this.viewDate, dir);
568
+ } else {
569
+ newDate = new Date(this.date);
570
+ newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
571
+ newViewDate = new Date(this.viewDate);
572
+ newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
573
+ }
574
+ if (this.dateWithinRange(newDate)){
575
+ this.date = newDate;
576
+ this.viewDate = newViewDate;
577
+ this.setValue();
578
+ this.update();
579
+ e.preventDefault();
580
+ dateChanged = true;
581
+ }
582
+ break;
583
+ case 13: // enter
584
+ this.hide();
585
+ e.preventDefault();
586
+ break;
587
+ case 9: // tab
588
+ this.hide();
589
+ break;
590
+ }
591
+ if (dateChanged){
592
+ this.element.trigger({
593
+ type: 'changeDate',
594
+ date: this.date
595
+ });
596
+ var element;
597
+ if (this.isInput) {
598
+ element = this.element;
599
+ } else if (this.component){
600
+ element = this.element.find('input');
601
+ }
602
+ if (element) {
603
+ element.change();
604
+ }
605
+ }
606
+ },
607
+
608
+ showMode: function(dir) {
609
+ if (dir) {
610
+ this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
611
+ }
612
+ this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
613
+ this.updateNavArrows();
614
+ }
615
+ };
616
+
617
+ $.fn.datepicker = function ( option ) {
618
+ var args = Array.apply(null, arguments);
619
+ args.shift();
620
+ return this.each(function () {
621
+ var $this = $(this),
622
+ data = $this.data('datepicker'),
623
+ options = typeof option == 'object' && option;
624
+ if (!data) {
625
+ $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
626
+ }
627
+ if (typeof option == 'string' && typeof data[option] == 'function') {
628
+ data[option].apply(data, args);
629
+ }
630
+ });
631
+ };
632
+
633
+ $.fn.datepicker.defaults = {
634
+ };
635
+ $.fn.datepicker.Constructor = Datepicker;
636
+ var dates = $.fn.datepicker.dates = {
637
+ en: {
638
+ days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
639
+ daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
640
+ daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
641
+ months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
642
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
643
+ today: "Today"
644
+ }
645
+ }
646
+
647
+ var DPGlobal = {
648
+ modes: [
649
+ {
650
+ clsName: 'days',
651
+ navFnc: 'Month',
652
+ navStep: 1
653
+ },
654
+ {
655
+ clsName: 'months',
656
+ navFnc: 'FullYear',
657
+ navStep: 1
658
+ },
659
+ {
660
+ clsName: 'years',
661
+ navFnc: 'FullYear',
662
+ navStep: 10
663
+ }],
664
+ isLeapYear: function (year) {
665
+ return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
666
+ },
667
+ getDaysInMonth: function (year, month) {
668
+ return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
669
+ },
670
+ validParts: /dd?|mm?|MM?|yy(?:yy)?/g,
671
+ nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g,
672
+ parseFormat: function(format){
673
+ // IE treats \0 as a string end in inputs (truncating the value),
674
+ // so it's a bad format delimiter, anyway
675
+ var separators = format.replace(this.validParts, '\0').split('\0'),
676
+ parts = format.match(this.validParts);
677
+ if (!separators || !separators.length || !parts || parts.length == 0){
678
+ throw new Error("Invalid date format.");
679
+ }
680
+ return {separators: separators, parts: parts};
681
+ },
682
+ parseDate: function(date, format, language) {
683
+ if (date instanceof Date) return date;
684
+ if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
685
+ var part_re = /([-+]\d+)([dmwy])/,
686
+ parts = date.match(/([-+]\d+)([dmwy])/g),
687
+ part, dir;
688
+ date = new Date();
689
+ for (var i=0; i<parts.length; i++) {
690
+ part = part_re.exec(parts[i]);
691
+ dir = parseInt(part[1]);
692
+ switch(part[2]){
693
+ case 'd':
694
+ date.setUTCDate(date.getUTCDate() + dir);
695
+ break;
696
+ case 'm':
697
+ date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
698
+ break;
699
+ case 'w':
700
+ date.setUTCDate(date.getUTCDate() + dir * 7);
701
+ break;
702
+ case 'y':
703
+ date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
704
+ break;
705
+ }
706
+ }
707
+ return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
708
+ }
709
+ var parts = date && date.match(this.nonpunctuation) || [],
710
+ date = new Date(),
711
+ parsed = {},
712
+ setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
713
+ setters_map = {
714
+ yyyy: function(d,v){ return d.setUTCFullYear(v); },
715
+ yy: function(d,v){ return d.setUTCFullYear(2000+v); },
716
+ m: function(d,v){
717
+ v -= 1;
718
+ while (v<0) v += 12;
719
+ v %= 12;
720
+ d.setUTCMonth(v);
721
+ while (d.getUTCMonth() != v)
722
+ d.setUTCDate(d.getUTCDate()-1);
723
+ return d;
724
+ },
725
+ d: function(d,v){ return d.setUTCDate(v); }
726
+ },
727
+ val, filtered, part;
728
+ setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
729
+ setters_map['dd'] = setters_map['d'];
730
+ date = UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
731
+ if (parts.length == format.parts.length) {
732
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
733
+ val = parseInt(parts[i], 10);
734
+ part = format.parts[i];
735
+ if (isNaN(val)) {
736
+ switch(part) {
737
+ case 'MM':
738
+ filtered = $(dates[language].months).filter(function(){
739
+ var m = this.slice(0, parts[i].length),
740
+ p = parts[i].slice(0, m.length);
741
+ return m == p;
742
+ });
743
+ val = $.inArray(filtered[0], dates[language].months) + 1;
744
+ break;
745
+ case 'M':
746
+ filtered = $(dates[language].monthsShort).filter(function(){
747
+ var m = this.slice(0, parts[i].length),
748
+ p = parts[i].slice(0, m.length);
749
+ return m == p;
750
+ });
751
+ val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
752
+ break;
753
+ }
754
+ }
755
+ parsed[part] = val;
756
+ }
757
+ for (var i=0, s; i<setters_order.length; i++){
758
+ s = setters_order[i];
759
+ if (s in parsed)
760
+ setters_map[s](date, parsed[s])
761
+ }
762
+ }
763
+ return date;
764
+ },
765
+ formatDate: function(date, format, language){
766
+ var val = {
767
+ d: date.getUTCDate(),
768
+ m: date.getUTCMonth() + 1,
769
+ M: dates[language].monthsShort[date.getUTCMonth()],
770
+ MM: dates[language].months[date.getUTCMonth()],
771
+ yy: date.getUTCFullYear().toString().substring(2),
772
+ yyyy: date.getUTCFullYear()
773
+ };
774
+ val.dd = (val.d < 10 ? '0' : '') + val.d;
775
+ val.mm = (val.m < 10 ? '0' : '') + val.m;
776
+ var date = [],
777
+ seps = $.extend([], format.separators);
778
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
779
+ if (seps.length)
780
+ date.push(seps.shift())
781
+ date.push(val[format.parts[i]]);
782
+ }
783
+ return date.join('');
784
+ },
785
+ headTemplate: '<thead>'+
786
+ '<tr>'+
787
+ '<th class="prev"><i class="icon-arrow-left"/></th>'+
788
+ '<th colspan="5" class="switch"></th>'+
789
+ '<th class="next"><i class="icon-arrow-right"/></th>'+
790
+ '</tr>'+
791
+ '</thead>',
792
+ contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
793
+ footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
794
+ };
795
+ DPGlobal.template = '<div class="datepicker dropdown-menu">'+
796
+ '<div class="datepicker-days">'+
797
+ '<table class=" table-condensed">'+
798
+ DPGlobal.headTemplate+
799
+ '<tbody></tbody>'+
800
+ DPGlobal.footTemplate+
801
+ '</table>'+
802
+ '</div>'+
803
+ '<div class="datepicker-months">'+
804
+ '<table class="table-condensed">'+
805
+ DPGlobal.headTemplate+
806
+ DPGlobal.contTemplate+
807
+ DPGlobal.footTemplate+
808
+ '</table>'+
809
+ '</div>'+
810
+ '<div class="datepicker-years">'+
811
+ '<table class="table-condensed">'+
812
+ DPGlobal.headTemplate+
813
+ DPGlobal.contTemplate+
814
+ DPGlobal.footTemplate+
815
+ '</table>'+
816
+ '</div>'+
817
+ '</div>';
818
+ }( window.jQuery );
@@ -0,0 +1,90 @@
1
+ #= require './bootstrap-datepicker'
2
+ #= require './templates/label'
3
+ #= require './templates/select'
4
+ #= require './templates/text_field'
5
+ #= require './templates/date_field'
6
+ #= require './templates/text_area'
7
+ #= require './templates/check_box'
8
+
9
+ class @FormHelper
10
+
11
+ constructor: (@model, @options) ->
12
+ if @options? and @options['name']?
13
+ @name = @options['name']
14
+ else
15
+ @name = @model.constructor.name.toLowerCase()
16
+
17
+ label: (attr, title, html_options={}) ->
18
+ if _.isObject(title) or !title?
19
+ [title, html_options] = [@attr2name(attr), {}]
20
+ JST['form_helper/templates/label'] @basics4attr(attr, title, html_options)
21
+
22
+ text_field: (attr, value, html_options={}) ->
23
+ if _.isObject(value)
24
+ [html_options, value] = [value, null]
25
+ JST['form_helper/templates/text_field'] @basics4attr(attr, value, html_options)
26
+
27
+ text_area: (attr, value, html_options={}) ->
28
+ if _.isObject(value)
29
+ [html_options, value] = [value, null]
30
+ JST['form_helper/templates/text_area'] @basics4attr(attr, value, html_options)
31
+
32
+ check_box: (attr, value, html_options={}) ->
33
+ if _.isObject(value)
34
+ [html_options, value] = [value, null]
35
+ title = html_options.title; delete html_options.title
36
+ attrs = @basics4attr(attr, value, html_options)
37
+ attrs.title = title
38
+ JST['form_helper/templates/check_box'] attrs
39
+
40
+ date_field: (attr, html_options={}) ->
41
+ JST['form_helper/templates/date_field'] @basics4attr(attr, value?, html_options)
42
+
43
+ select: (attr, options, html_options={}) ->
44
+ attrs = @basics4attr(attr, html_options)
45
+ attrs.values = options.values
46
+ attrs.value ||= options.selected
47
+ JST['form_helper/templates/select'] attrs
48
+
49
+ select_tag: (name, options, html_options={}) ->
50
+ attrs =
51
+ field_name : name
52
+ values : options.values
53
+ value : options.selected
54
+ unfold_options : @unfold_options html_options
55
+ field_id : name
56
+ JST['form_helper/templates/select'] attrs
57
+
58
+ #
59
+ #
60
+ #
61
+ field_id: (attr) ->
62
+ "#{@name}_#{attr}"
63
+
64
+ field_name: (attr) ->
65
+ "#{@name}[#{attr}]"
66
+
67
+ unfold_options: (options) ->
68
+ unfolded = for key, value of options
69
+ "#{key}=\"#{value}\""
70
+ unfolded.join(' ')
71
+
72
+ attr2name: (attr) ->
73
+ attr = @capitalize attr
74
+ attr = attr.replace /_/g, ' '
75
+ attr
76
+
77
+ basics4attr: (attr, value, html_options) ->
78
+ errors = @model.errors?[attr] or @model.get('errors')?[attr]
79
+ if errors
80
+ html_options.class = html_options.class?.concat(' error') or 'error'
81
+ attrs =
82
+ unfold_options: @unfold_options html_options
83
+ field_id: @field_id(attr)
84
+ field_name: @field_name(attr)
85
+ value: value or @model.get attr
86
+ errors: errors
87
+ attrs
88
+
89
+ capitalize: (str) ->
90
+ str.charAt(0).toUpperCase() + str.slice(1);
@@ -0,0 +1,6 @@
1
+ <label class="checkbox">
2
+ <input id="<%= @field_id %>" name="<%= @field_name %>" type="hidden" value="false">
3
+ <input id="<%= @field_id %>" name="<%= @field_name %>" type="checkbox"
4
+ value="true" <%= 'checked' if _.contains([1,'1',true,'true'], @value) %>>
5
+ <%= @title %>
6
+ </label>
@@ -0,0 +1,15 @@
1
+ <div class="input-append date datepicker" data-date="<%= @value %>" data-date-format="dd-mm-yyyy">
2
+ <input id="<%= @field_id %>" name="<%= @field_name %>"
3
+ size="16" type="text" value="<%= @value %>" <%- @unfold_options %>>
4
+ <span class="add-on"><i class="icon-th"></i></span>
5
+ </div>
6
+ <span class="help-inline">
7
+ <%= @errors if @errors %>
8
+ </span>
9
+ <script>
10
+ $(function(){
11
+ $('.datepicker input').datepicker({
12
+ autoclose: true
13
+ })
14
+ })
15
+ </script>
@@ -0,0 +1,3 @@
1
+ <label for="<%= @field_id %>" <%- @unfold_options %>>
2
+ <%= @value %>
3
+ </label>
@@ -0,0 +1,5 @@
1
+ <select <%- @unfold_options %> id="<%= @field_id %>" name="<%= @field_name %>">
2
+ <% for option in @values: %>
3
+ <option data="<%= @value %>" <%= "selected" if @value is option[0] %> value="<%= option[0] %>"><%= option[1] %></option>
4
+ <% end %>
5
+ </select>
@@ -0,0 +1,5 @@
1
+ <textarea id="<%= @field_id %>" <%- @unfold_options %>
2
+ name="<%= @field_name %>"><%= @value %></textarea>
3
+ <span class="help-inline">
4
+ <%= @errors if @errors %>
5
+ </span>
@@ -0,0 +1,5 @@
1
+ <input type="text" id="<%= @field_id %>" <%- @unfold_options %>
2
+ name="<%= @field_name %>" value="<%= @value %>" />
3
+ <span class="help-inline">
4
+ <%= @errors if @errors %>
5
+ </span>
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backbone_form_helper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.alpha
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Zdenal
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-17 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Form helper for backbone based on Rails priciple -> f.text_field :name
15
+ email:
16
+ - nevralaz@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
26
+ - backbone_form_helper.gemspec
27
+ - lib/backbone_form_helper.rb
28
+ - lib/backbone_form_helper/engine.rb
29
+ - lib/backbone_form_helper/version.rb
30
+ - vendor/assets/javascripts/form_helper/bootstrap-datepicker.js
31
+ - vendor/assets/javascripts/form_helper/form_helper.js.coffee
32
+ - vendor/assets/javascripts/form_helper/templates/check_box.jst.eco
33
+ - vendor/assets/javascripts/form_helper/templates/date_field.jst.eco
34
+ - vendor/assets/javascripts/form_helper/templates/label.jst.eco
35
+ - vendor/assets/javascripts/form_helper/templates/select.jst.eco
36
+ - vendor/assets/javascripts/form_helper/templates/text_area.jst.eco
37
+ - vendor/assets/javascripts/form_helper/templates/text_field.jst.eco
38
+ homepage: http://github.com/zdenal/backbone_form_helper.git
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>'
54
+ - !ruby/object:Gem::Version
55
+ version: 1.3.1
56
+ requirements: []
57
+ rubyforge_project: backbone_form_helper
58
+ rubygems_version: 1.8.24
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Backbone form helper
62
+ test_files: []