polymer-rails-forms 0.1.15 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 99e7b1677ddc0f20f5fd3b099d441374ef2026e7
4
- data.tar.gz: 70aa1538f6ff43a9f410aee9234ac5f2f39825d8
3
+ metadata.gz: ddb55e7abf7c864da70f1a3c27bef1c123444db2
4
+ data.tar.gz: e28459f4839d74416d98189908cc64e2cb406b9a
5
5
  SHA512:
6
- metadata.gz: 1e31bb104ede0d50094ccf06a6967b2a4f66b320f7556881a984d4f2d0c9224aad51ecbb135626d965a4c996dcac7a5c75dc40a72104ccee02473dd164371376
7
- data.tar.gz: 50e87d98b438f12bb271feab02ad8a95cd1b75a015ce99f0fa1c2c5c5c8596464d14e11e1f8da254bcc08f544e6d89d96a8099035f3624f6fd44690d9f57dcad
6
+ metadata.gz: 476fc7749837a3ad74e61b4468de204f1c9e159068e34e3c26cca011a037290a00fcdea05ff886bf49b907ac216181f8db40bf84184f4a357b22ae43d4d56789
7
+ data.tar.gz: 571d731a8643a554bc9226fcd54af5d88e016edb05e3950651951ffd416b6454eaf43ad49eb14ba79f186a8568a8c9a6cd2d6457d7274556646484f10bd72a15
data/README.md CHANGED
@@ -29,7 +29,7 @@ then in your /app/assets/components/application.html file add
29
29
  This is what a simple login form could look like using this gem
30
30
 
31
31
  ```html
32
- <rails-form id='sign_in_form' action="/users/sign_in" methos="POST" scope="user" submitText="Sign In"></rails-form>
32
+ <rails-form id='sign_in_form' action="/users/sign_in" method="POST" scope="user" submitText="Sign In"></rails-form>
33
33
  <script>
34
34
  document.getElementById("sign_in_form").setAttribute("structure", JSON.stringify({
35
35
  email: { type: 'string', label: "Email Address" },
@@ -85,8 +85,8 @@ again, but this time with nested location attributes
85
85
 
86
86
  ready: function(){
87
87
  this.structure = {
88
- email: { type: 'string', label: "Email Address" },
89
- password: { type: 'password' },
88
+ email: { type: 'string', label: "Email Address", required: true },
89
+ password: { type: 'password', required: true },
90
90
  location: { type: 'nest', allowAdd: false, multiple: false, structure: {
91
91
  address: { type: "string" },
92
92
  city: { type: "string" },
@@ -106,6 +106,11 @@ to set ```multiple: true``` the name would become ```user[location_attributes][0
106
106
  you were to set ```allowAdd: true``` the inputs would be in a list with the option to create more.
107
107
 
108
108
 
109
+ ###Validations
110
+
111
+ Also not that I've included ```required: true``` on the email and password fields. This means this triggers
112
+ them to be validated onSubmit and onChange. For a custome validation just use ```validates: 'method_name'```
113
+
109
114
  ####Note:
110
115
 
111
116
  If you're going to use the domReady function in your custom form element, be sure to call
@@ -118,6 +123,12 @@ functionality though, just make sure you run the ```this.appendInputs()``` so th
118
123
  To make the form ajaxy just include the form element's ```xhr='true'``` param
119
124
  and override the ```handleXhrCallback``` function.
120
125
 
126
+
127
+ ###Selects
128
+
129
+ selects are the same as any other input except that they also require the values property which is and array of
130
+ arrays, so ```[[value1, text2], [value2, text2]]```
131
+
121
132
  ##What's supported what's not
122
133
 
123
134
  ###So far the field types that are supported are:
@@ -138,7 +149,6 @@ and override the ```handleXhrCallback``` function.
138
149
  ###What's not supported
139
150
 
140
151
  * radio buttons
141
- * selects
142
152
  * ranges
143
153
  * everything else
144
154
 
@@ -0,0 +1,101 @@
1
+ <polymer-element name="form-spinner">
2
+ <template>
3
+ <style>
4
+ .spinner {
5
+ width: {{ width }}{{ unit }};
6
+ height: {{ height }}{{ unit }};
7
+ position: relative;
8
+ display: block;
9
+ }
10
+
11
+ .circle {
12
+ width: 25%;
13
+ height: 25%;
14
+ border-radius: 50%;
15
+ background-color: white;
16
+ position: absolute;
17
+ opacity: 0;
18
+
19
+ transition: opacity 300ms ease-out;
20
+ }
21
+
22
+ .fade.circle {
23
+ opacity: 1;
24
+ }
25
+
26
+ .circle_row_1 {
27
+ top: 0;
28
+ }
29
+
30
+ .circle_row_2 {
31
+ top: 50%;
32
+ margin-top: -12.5%;
33
+ }
34
+
35
+ .circle_row_3 {
36
+ bottom: 0;
37
+ }
38
+
39
+ .circle_column_1 {
40
+ left: 0;
41
+ }
42
+
43
+ .circle_column_2 {
44
+ left: 33.3%;
45
+ margin-left: -12.5%;
46
+ }
47
+
48
+ .circle_column_3 {
49
+ left: 66.6%;
50
+ margin-left: -12.5%;
51
+ }
52
+
53
+ .circle_column_4 {
54
+ right: 0;
55
+ }
56
+ </style>
57
+ <div class="spinner">
58
+ <div class="circle circle_row_1 circle_column_2 circle6"></div>
59
+ <div class="circle circle_row_1 circle_column_3 circle1"></div>
60
+ <div class="circle circle_row_2 circle_column_1 circle5"></div>
61
+ <div class="circle circle_row_2 circle_column_4 circle2"></div>
62
+ <div class="circle circle_row_3 circle_column_2 circle4"></div>
63
+ <div class="circle circle_row_3 circle_column_3 circle3"></div>
64
+ </div>
65
+ </template>
66
+
67
+ <script>
68
+ Polymer({
69
+ publish: {
70
+ width: 2,
71
+ height: 2,
72
+ unit: 'em'
73
+ },
74
+
75
+ start: function(){
76
+ console.log("starting")
77
+ var index = 1,
78
+ alt = 4,
79
+ spinner = this.shadowRoot.querySelector('.spinner');
80
+
81
+ spinner.classList.add('active');
82
+ var t = setInterval(function(){
83
+ if (spinner.classList.contains("active") === -1){
84
+ clearInterval(t);
85
+ return;
86
+ }
87
+ spinner.querySelector(".circle" + index).classList.add("fade");
88
+ spinner.querySelector(".circle" + alt).classList.remove("fade");
89
+
90
+ index = index + 1 > 6 ? 1 : index + 1;
91
+ alt = alt + 1 > 6 ? 1 : alt + 1;
92
+ }.bind(this), 100);
93
+ },
94
+
95
+ stop: function(){
96
+ console.log("stop")
97
+ this.shadowRoot.querySelector('.spinner').classList.remove('active');
98
+ }
99
+ });
100
+ </script>
101
+ </polymer-element>
@@ -41,7 +41,15 @@
41
41
 
42
42
  hasClass: function(el, cls){
43
43
  var reg = new RegExp("\\s+" + cls, "g")
44
- return reg.test(el.className);
44
+ return reg.test(el.className) || el.className === cls;
45
+ },
46
+
47
+ toggleClass: function(el, cls){
48
+ if (this.hasClass(el, cls)){
49
+ this.removeClass(el, cls);
50
+ } else {
51
+ this.addClass(el, cls);
52
+ }
45
53
  },
46
54
 
47
55
  indexStore: {},
@@ -54,6 +62,12 @@
54
62
  return key;
55
63
  },
56
64
 
65
+ getIndex: function(obj){
66
+ for (var x in this.indexStore){
67
+ if (this.indexStore[x].obj === obj) return this.indexStore[x].index;
68
+ }
69
+ },
70
+
57
71
  addToIndexStore: function(obj, index){
58
72
  var key = this.genIndexStoreKey();
59
73
  this.indexStore[key] = {obj: obj, index: index}
@@ -73,19 +87,21 @@
73
87
 
74
88
  enumerate: function(obj){
75
89
  //TODO: keygen should check for uniquness
76
- for (var i=0; i<obj.length; i++){
77
- this.addToIndexStore(obj[i], i)
90
+ var used = [];
91
+ for (var i=0; i<obj.length; i++){
92
+ var current_index = this.getIndex(obj[i]);
93
+ if (current_index) used.push();
94
+ }
95
+
96
+ for (var i=0; i<obj.length; i++){
97
+ var index = i;
98
+ while (used.indexOf(index) !== -1){ index++ }
99
+ this.addToIndexStore(obj[i], index);
78
100
  }
79
101
 
80
102
  return obj
81
103
  },
82
104
 
83
- getIndex: function(obj){
84
- for (var x in this.indexStore){
85
- if (this.indexStore[x].obj === obj) return this.indexStore[x].index;
86
- }
87
- },
88
-
89
105
  /* Type Checks */
90
106
 
91
107
  getKeys: function(o){
@@ -135,7 +151,6 @@
135
151
  }
136
152
  }
137
153
  } else {
138
- console.log(!(o[x] === null || o[x].length === 0))
139
154
  if (!(o[x] === null || o[x].length === 0)){
140
155
  blank = false;
141
156
  break;
@@ -206,7 +221,12 @@
206
221
  var data = this.classToData(t.className),
207
222
  newData = {};
208
223
 
209
- this.addToIndexStore(newData, data.length);
224
+ var index = data.length,
225
+ used = [];
226
+ for (var i=0; i<data.length; i++){ used.push(this.getIndex(data[i])) }
227
+ while (used.indexOf(index) !== -1){ index++ }
228
+
229
+ this.addToIndexStore(newData, index);
210
230
  data.push(newData)
211
231
  } else {
212
232
  invalid.focus();
@@ -214,18 +234,25 @@
214
234
  },
215
235
 
216
236
  updateScope: function(key){
217
- var scope = this.scope + "[" + this.key + "_attributes]";
237
+ var scope = this.structure[this.key].unscoped ? this.key : this.scope + "[" + this.key + "_attributes]";
238
+
218
239
  this.scope = scope;
219
240
  return this.scope;
220
241
  },
221
242
 
222
243
  scopeFieldData: function(data, key){
223
- if (data[key] === void(0)) data[key] = null;
224
- return data;
244
+ data = this.structure[this.key].unscoped ? this.unscopedData : data;
245
+ //console.log("field", key, data[key]);
246
+ if (this.structure[this.key].type !== 'html'){
247
+ if (data[key] === void(0)) data[key] = this.structure[this.key].value || null;
248
+ return data;
249
+ }
225
250
  },
226
251
 
227
252
  scopeNestData: function(data, key){
228
- if (this.structure[key].multiple){
253
+ data = this.structure[this.key].unscoped ? this.unscopedData : data;
254
+ //console.log("nest", key, data[key]);
255
+ if (this.structure[key].multiple){
229
256
  if (data[key] === void(0)){
230
257
  if (this.structure[key].allowAdd) {
231
258
  data[key] = [];
@@ -325,9 +352,8 @@
325
352
  createListItem: function(obj){
326
353
  var className = this.listItemClassName(obj),
327
354
  isEmpty = Object.keys(obj).length === 0,
328
- container = document.createElement("div");
329
- this.addClass(container, "list-item-display")
330
-
355
+ container = this.createWithAttributes("div", {'class': "list-item-display"});
356
+
331
357
  if (this.structure[this.key].display_fields){
332
358
  for (var i=0; i<this.structure[this.key].display_fields.length; i++){
333
359
  var field = this.structure[this.key].display_fields[i],
@@ -336,7 +362,8 @@
336
362
 
337
363
  var el = document.createElement("div"),
338
364
  label = this.createWithAttributes("span", { text: this.capitalize(nameField.replace(/_/g, " ")) + ": " });
339
-
365
+
366
+
340
367
  var value = document.createElement("span"),
341
368
  val = document.createTextNode("");
342
369
 
@@ -357,17 +384,39 @@
357
384
  if (!obj[baseField]) obj[baseField] = "";
358
385
  val.bind("textContent", new PathObserver(obj, field));
359
386
  }
360
-
361
- el.appendChild(label)
362
- el.appendChild(value)
363
- value.appendChild(val)
387
+
388
+ el.appendChild(label);
389
+ el.appendChild(value);
390
+ value.appendChild(val);
364
391
 
365
392
  container.appendChild(el);
366
393
  }
367
394
  } else {
368
395
  container.appendChild(this.createWithAttributes("div", {text: "item " + (this.getIndex(obj) + 1)}))
369
396
  }
370
-
397
+
398
+ var remove_button = this.createWithAttributes("core-icon", {icon: 'clear'});
399
+ remove_button.addEventListener("click", function(e){
400
+ e.stopPropagation();
401
+ var clone = this.parentData.slice(0);
402
+ clone.splice(clone.indexOf(obj), 1);
403
+
404
+ while (this.parentData.length){
405
+ this.removeFromIndexStore(this.parentData[0]);
406
+ this.parentData.shift();
407
+ }
408
+
409
+ this.async(function(){
410
+ for (var i=0; i<clone.length; i++){
411
+ this.addToIndexStore(clone[i], i);
412
+ this.parentData.push(clone[i]);
413
+ }
414
+ });
415
+
416
+ }.bind(this), false)
417
+
418
+ container.appendChild(remove_button);
419
+
371
420
  var self = this;
372
421
  this.inputList.push({ element: container, container: ".list-item." + className, events: {} })
373
422
 
@@ -432,14 +481,26 @@
432
481
  },
433
482
 
434
483
  scopeToClass: function(key){
435
- var className = this.scope.replace(/\[/g, "_").replace(/\]/g, "") + "_" + key;
436
- return className;
484
+ if (this.structure[this.key].unscoped){
485
+ var className = key
486
+ } else {
487
+ var className = this.scope.replace(/\[/g, "_").replace(/\]/g, "") + "_" + key;
488
+ }
489
+ return className;
437
490
  },
438
491
 
439
- classToData: function(className){
440
- var cn = className.split("_").slice(1).join("_"),
441
- data = this.data;
492
+ classToData: function(cn){
493
+ var self = this,
494
+ data = this.unscopedData;
442
495
 
496
+ for (var x in self.unscopedData){
497
+ if (cn.substr(0, x.length) === x){
498
+ cn = cn.replace(new RegExp(x + "_?"), "");
499
+ data = data[x];
500
+ break;
501
+ }
502
+ }
503
+
443
504
  function nest(str){
444
505
  var p = str.split("_attributes_");
445
506
  if (p.length > 1){
@@ -463,7 +524,7 @@
463
524
  }
464
525
 
465
526
  while (nest(cn) || arr(cn)){ }
466
- return data[cn];
527
+ return cn.length > 0 ? data[cn] : data;
467
528
  },
468
529
 
469
530
  checkForBlank: function(group){
@@ -551,6 +612,96 @@
551
612
  return wrapper;
552
613
  }.bind(this),
553
614
 
615
+ select: function(){
616
+ var structure = this.structure[this.key],
617
+ values = structure.values;
618
+
619
+ var wrapper = this.createWithAttributes("div", {"class": "select-wrapper"}),
620
+ decorator = this.createWithAttributes("gc-input-decorator", { label: this.capitalize(structure.label || this.key), floatingLabel: structure.options.floatingLabel}),
621
+ secretInput = this.createWithAttributes('input', {type: 'hidden', name: this.updateFieldScope()}),
622
+ input = this.createWithAttributes("input", { type: "text", name: "", "class": "pseudo-select-input", disabled: true }),
623
+ select = this.createWithAttributes("core-selector", {valueattr: "val", "class": "pseudo-select", multi: (!!structure.multi) });
624
+
625
+ secretInput.bind('value', new PathObserver(this.data, this.key));
626
+ select.bind('selected', new PathObserver(this.data, this.key));
627
+
628
+ if (!structure.blank){
629
+ this.data[this.key] = values.length > 0 ? values[0][0] : "";
630
+ input.value = values.length > 0 ? values[0][1] : "";
631
+ }
632
+ var self = this;
633
+
634
+ select.addEventListener("click", function(e){
635
+ if (!!self.structure[self.key].multi) e.stopPropagation();
636
+ }, false);
637
+
638
+ decorator.addEventListener('click', function(e){
639
+ if (self.hasClass(wrapper, 'opened')) return;
640
+
641
+ e.stopPropagation();
642
+ decorator.updateLabelVisibility("down");
643
+ self.addClass(wrapper, 'opened');
644
+
645
+ window.addEventListener('click', function(){
646
+ self.removeClass(wrapper, 'opened');
647
+ window.removeEventListener('click', arguments.callee);
648
+ }, false);
649
+ }, false);
650
+
651
+ select.addEventListener('core-select', function(){
652
+ if (!self.structure[self.key].multi) self.removeClass(wrapper, 'opened');
653
+ decorator.updateLabelVisibility(select.selected ? select.selected : "");
654
+
655
+ self.async(function(){
656
+ if (!!structure.multi){
657
+ input.value = select === null ? "" : select.selectedItem.map(function(s){ return s.textContent }).join(', ');
658
+ secretInput.value = JSON.stringify( select.selectedItem.map(function(s){ return s.getAttribute('val') }) );
659
+ } else {
660
+ input.value = select === null ? "" : select.selectedItem.textContent;
661
+ secretInput.value = select.selectedItem.getAttribute('val');
662
+ }
663
+ });
664
+ }, false);
665
+
666
+ var build_options = function(){
667
+ while (select.firstChild) { select.removeChild(select.firstChild) };
668
+ if (structure.blank) select.appendChild(this.createWithAttributes("div", {val: "", text: "" }))
669
+ for (var i=0; i<structure.values.length; i++){
670
+ var div = self.createWithAttributes("div", {val: structure.values[i][0], text: structure.values[i][1] });
671
+
672
+ if (!!structure.multi){
673
+ var checkbox = this.createWithAttributes("paper-checkbox", {"class": "select-check"});
674
+ div.appendChild(checkbox);
675
+ div.addEventListener("click", function(el, box){
676
+ this.async(function(){
677
+ box.checked = this.hasClass(el, 'core-selected');
678
+ }.bind(this));
679
+ }.bind(this, div, checkbox), false)
680
+ }
681
+ select.appendChild(div);
682
+ }
683
+
684
+ var nil = structure.values.length === 0 ? self.addClass(wrapper, 'is-blank') : self.removeClass(wrapper, 'is-blank');
685
+ nil = structure.values.length === 1 ? self.addClass(wrapper, 'is-single') : self.removeClass(wrapper, 'is-single');
686
+ }.bind(this);
687
+
688
+ build_options();
689
+ new PathObserver(structure, 'values').open(function(){
690
+ build_options();
691
+ })
692
+
693
+ new PathObserver(structure, 'label').open(function(){
694
+ decorator.label = this.capitalize(this.structure[this.key].label || this.key)
695
+ }.bind(this));
696
+
697
+ wrapper.appendChild(decorator);
698
+ decorator.appendChild(this.createWithAttributes("core-icon", {icon: "expand-more", "class": 'select-icon'}));
699
+ decorator.appendChild(input);
700
+ wrapper.appendChild(select);
701
+ wrapper.appendChild(secretInput);
702
+ return wrapper;
703
+ }.bind(this),
704
+
554
705
  location: function(){
555
706
  var wrapper = document.createElement("gc-input-decorator"),
556
707
  input = document.createElement( "input" );
@@ -576,8 +727,8 @@
576
727
  self.data[self.key].lat = place.geometry.location.lat();
577
728
  self.data[self.key].lng = place.geometry.location.lng();
578
729
 
579
- if (self.structure[self.key].options.events.select){
580
- self.structure[self.key].options.events.select(self.data[self.key])
730
+ if (!!self.structure[self.key].events.select){
731
+ self.structure[self.key].events.select(self.data[self.key])
581
732
  }
582
733
  })
583
734
 
@@ -610,9 +761,13 @@
610
761
  wrapper.parentNode.style.backgroundImage = "url(" + oFREvent.target.result + ")";
611
762
  self.addClass(wrapper.parentNode, "has-image");
612
763
  };
764
+
765
+ self.data[self.key] = this.files[0];
613
766
  } else {
614
767
  self.removeClass(wrapper.parentNode, 'has-image');
615
768
  wrapper.parentNode.style.backgroundImage = "";
769
+
770
+ self.data[self.key] = null;
616
771
  }
617
772
  }, true)
618
773
 
@@ -664,6 +819,7 @@
664
819
 
665
820
  input.type = type;
666
821
  input.name = name || this.updateFieldScope();
822
+ input.value = this.structure[this.key].value || '';
667
823
  input.id = this.scopeToClass(this.key)
668
824
  if (this.structure[this.key].required) input.required = true;
669
825
  input.bind('value', new PathObserver(this.data, this.key))
@@ -683,21 +839,20 @@
683
839
  createJSONField: function(fields){
684
840
  //TODO: only basic JSON support, if you want crazy complicated stuff, just use a textarea or custom input
685
841
  var wrapper = this.createWithAttributes('div', { "class": "json-wrapper" }),
686
- input = this.createWithAttributes("textarea", { name: this.updateFieldScope(), "class": "hidden" }),
842
+ input = this.createWithAttributes("textarea", { name: this.updateFieldScope(), "class": "hidden", text: JSON.stringify(this.data[this.key]) }),
687
843
  isList = this.parentStructure !== null && this.parentStructure[this.parentContext].allowAdd;
688
844
 
689
845
  if (!this.data[this.key]) this.data[this.key] = {};
690
-
691
- var observer = new ObjectObserver(this.data[this.key])
692
- observer.open(function(){
846
+
847
+ new ObjectObserver(this.data[this.key]).open(function(){
693
848
  input.value = JSON.stringify(this.data[this.key])
694
849
  }.bind(this))
695
850
 
696
- input.bind('value', new PathObserver(this.data, this.key))
851
+ // input.bind('value', new PathObserver(this.data, this.key))
697
852
 
698
853
  wrapper.appendChild(input);
699
854
  for (var i=0; i<fields.length; i++){
700
- this.data[this.key][fields[i]] = "";
855
+ this.data[this.key][fields[i]] = this.data[this.key][fields[i]] || "";
701
856
  var input_wrapper = this.createWithAttributes("div", { "class": (isList ? "input-wrapper" : "input") + " json-input-wrapper"})
702
857
  decorator = document.createElement("gc-input-decorator"),
703
858
  el = this.createWithAttributes('input');
@@ -709,8 +864,6 @@
709
864
 
710
865
  el.bind("value", new PathObserver(this.data[this.key], fields[i]))
711
866
 
712
- //TODO: create a parentStruture thing. check the parent structure for isNull or allowAdd
713
- //change the container selector appropriately
714
867
  if (isList){
715
868
  var className = "." + this.parentContext + "-" + this.getIndex(this.data) + " .input-wrapper." + this.key;
716
869
  var o = { element: input_wrapper, container: className, events: {}, position: "before" }
@@ -719,7 +872,6 @@
719
872
  }
720
873
 
721
874
  this.inputList.push(o)
722
- //wrapper.appendChild(decorator);
723
875
  }
724
876
 
725
877
  return wrapper;
@@ -1,18 +1,22 @@
1
1
  <script>
2
2
  var Validation = {
3
3
  errors: {},
4
+ validatedInputs: {},
4
5
 
5
6
  addValidation: function(type, input){
6
7
  //TODO should validate everything
7
8
  if (this.structure[this.key].required){
8
- input.addEventListener("change", function(){
9
- this.validate(this.structure[this.key].type, input);
10
- }.bind(this), false)
9
+ var fn = function(){ this.validate(this.structure[this.key].type, input); }.bind(this)
11
10
  } else if (!!this.structure[this.key].validates){
12
- input.addEventListener("change", function(){
13
- this[this.structure[this.key].validates](type, input)
14
- }.bind(this), false)
11
+ var fn = function(){ this[this.structure[this.key].validates](type, input) }.bind(this)
12
+ } else {
13
+ var fn = function(){ return false; }
15
14
  }
15
+
16
+ var key = this.genIndexStoreKey();
17
+ this.validatedInputs[key] = {input: input, fn: fn};
18
+ input.addEventListener('change', fn, false);
19
+ input.addEventListener('DOMNodeRemoved', function(){ delete this.validatedInputs[key] }.bind(this), false);
16
20
  },
17
21
 
18
22
  invalidate: function(type, input, isInvalid){
@@ -65,8 +69,7 @@
65
69
  }.bind(this),
66
70
 
67
71
  integer: function(){
68
- console.log("checking integer", isNaN(input.value))
69
- return !(required && !isNaN(input.value))
72
+ return !(required && input.value.length > 0 && !isNaN(input.value))
70
73
  }.bind(this),
71
74
 
72
75
  url: function(){
@@ -91,7 +94,11 @@
91
94
 
92
95
  image: function(){
93
96
  return !(required && input.value.length > 0)
94
- }.bind(this)
97
+ }.bind(this),
98
+
99
+ select: function(){
100
+ return false;
101
+ }
95
102
  }
96
103
 
97
104
  var isInvalid = (!!validators[type] && validators[type]())
@@ -100,6 +107,17 @@
100
107
  this.invalidate(type, input, isInvalid);
101
108
  }
102
109
 
110
+ return isInvalid;
111
+ },
112
+
113
+ isFormInvalid: function(){
114
+ var isInvalid = false;
115
+ for (var x in this.validatedInputs){
116
+ if (this.validatedInputs[x].fn()){
117
+ isInvalid = true;
118
+ }
119
+ }
120
+
103
121
  return isInvalid;
104
122
  }
105
123
  }
@@ -1,6 +1,7 @@
1
1
  <link rel="import" href="rails-form-helpers.html" >
2
2
  <link rel="import" href="rails-form-validators.html" >
3
3
  <link rel="import" href="gc-paper-decorator.html" >
4
+ <link rel="import" href="form-spinner.html" >
4
5
  <link rel="import" href="../paper-button/paper-button.html" >
5
6
  <link rel="import" href="../core-icons/image-icons.html" >
6
7
  <link rel="import" href="../core-ajax/core-xhr.html" >
@@ -11,6 +12,9 @@
11
12
  <template>
12
13
  <link rel='stylesheet' href='pikaday.css'>
13
14
  <style>
15
+ :host {
16
+ display: block;
17
+ }
14
18
  paper-button {
15
19
  font-size: 14px;
16
20
  }
@@ -22,11 +26,26 @@
22
26
 
23
27
  #submit-button {
24
28
  margin-top: 15px;
29
+ margin-bottom: 5px;
25
30
  padding-left: 30px;
26
31
  padding-right: 30px;
27
32
  background-color: #5bc0de;
28
33
  border-color: #46b8da;
29
34
  color: white;
35
+ clear: both;
36
+ margin-right: 15px;
37
+ }
38
+
39
+ #submit-button.in-transit span{
40
+ display: none;
41
+ }
42
+
43
+ #submit-button.in-transit form-spinner {
44
+ display: block;
45
+ }
46
+
47
+ #submit-button form-spinner {
48
+ display: none;
30
49
  }
31
50
 
32
51
  gc-input-decorator {
@@ -99,6 +118,16 @@
99
118
  min-height: 55px;
100
119
  }
101
120
 
121
+ .list-wrapper paper-button {
122
+ max-width: 28%;
123
+ margin: 0px;
124
+ }
125
+
126
+ .list-wrapper paper-button /deep/ .button-content{
127
+ -webkit-justify-content: flex-start !important;
128
+ justify-content: flex-start !important;
129
+ }
130
+
102
131
  .list-wrapper .list {
103
132
  overflow: auto;
104
133
  position: relative;
@@ -126,6 +155,16 @@
126
155
  box-sizing: border-box;
127
156
  }
128
157
 
158
+ .list-group .list-item core-icon {
159
+ height: 0px;
160
+ border: solid 1px #A1A1A1;
161
+ padding: 7px;
162
+ width: 0px;
163
+ border-radius: 8px;
164
+ color: red;
165
+ margin-right: 10px;
166
+ }
167
+
129
168
  .list-group .list-form {
130
169
  display: none;
131
170
  }
@@ -136,12 +175,65 @@
136
175
 
137
176
  .list-group.focused .list-form, .list-group.is-invalid .list-form, .list-group.is-blank .list-form {
138
177
  display: block;
178
+ overflow: hidden;
139
179
  }
140
180
 
141
181
  paper-checkbox input[type=checkbox] {
142
182
  display: none;
143
183
  }
144
184
 
185
+ .select-wrapper {
186
+ position: relative;
187
+ }
188
+
189
+ .select-wrapper .pseudo-select {
190
+ opacity: 0;
191
+ display: none;
192
+ transition: opacity 250ms ease-out;
193
+ position: absolute;
194
+ top: 100%;
195
+ width: 106%;
196
+ background-color: white;
197
+ box-shadow: -2px 1px 10px #888;
198
+ margin-top: -11px;
199
+ z-index: 1;
200
+ margin-left: -3%;
201
+ }
202
+
203
+ .select-wrapper.opened .pseudo-select {
204
+ opacity: 1;
205
+ display: block;
206
+ }
207
+
208
+ .select-wrapper input.pseudo-select-input {
209
+ padding-right: 20px;
210
+ box-sizing: border-box;
211
+ }
212
+
213
+ .select-wrapper .select-icon {
214
+ position: absolute;
215
+ right: -6px;
216
+ width: 35px;
217
+ bottom: -6px;
218
+ height: 35px;
219
+ }
220
+
221
+ .select-wrapper .pseudo-select > div {
222
+ min-height: 2em;
223
+ border-bottom: solid 1px;
224
+ padding: 5px 3%;
225
+ position: relative;
226
+ }
227
+
228
+ .select-wrapper .pseudo-select > div:hover {
229
+ background-color: #D5DAFF;
230
+ }
231
+
232
+ .select-wrapper .pseudo-select > div paper-checkbox {
233
+ float: left;
234
+ margin-right: 15px;
235
+ }
236
+
145
237
  </style>
146
238
  <!-- TODO: structures should be arrays to preserve order -->
147
239
  <form id="rails_form" _action="{{ action }}" method="{{ method }}" enctype="multipart/form-data">
@@ -197,7 +289,11 @@
197
289
  </template>
198
290
  </template>
199
291
  <div class='sumbit-button-wrapper'>
200
- <paper-button id='submit-button' raised>{{ submitText }}</paper-button>
292
+ <paper-button id='submit-button' raised>
293
+ <span class='submit-text'>{{ submitText }}</span>
294
+ <form-spinner height="1.5" width="1.5"></form-spinner>
295
+ </paper-button>
296
+ <!-- <paper-button id='test-button' raised>Test</paper-button> -->
201
297
  <template if="{{ xhr }}">
202
298
  <core-xhr id='xhr'></core-xhr>
203
299
  </template>
@@ -229,12 +325,14 @@
229
325
  data: null,
230
326
  submitText: "Submit",
231
327
  xhr: false,
232
- onSubmit: null
328
+ unscopedData: null
233
329
  },
234
330
 
235
331
  parentContext: null,
236
332
  parentStructure: null,
237
333
  parentData: null,
334
+
335
+ inTransit: false,
238
336
 
239
337
  toArray: function(str){
240
338
  return [str];
@@ -242,21 +340,45 @@
242
340
 
243
341
  created: function(){
244
342
  this.structure = {};
245
- this.data = {};
246
343
  this.inputList = [];
344
+ this.indexStore = {};
345
+
346
+ var data = this.data === null ? {} : this.data;
347
+ if (!!this.scope && this.scope.replace(/\s/g, "").length > 0){
348
+ if (data[this.scope] === void(0)) data[this.scope] = {};
349
+ this.data = data[this.scope];
350
+ } else {
351
+ this.data = data;
352
+ }
353
+
354
+ this.unscopedData = data;
355
+ this.unscopedScope = this.scope;
247
356
  },
248
357
 
249
358
  handleXhrCallback: function(){
250
-
359
+
251
360
  },
252
361
 
253
362
  domReady: function(){
254
363
  var form = this.shadowRoot.querySelector("form"),
255
- button = this.shadowRoot.querySelector("#submit-button");
256
-
364
+ button = this.shadowRoot.querySelector("#submit-button"),
365
+ test = this.shadowRoot.querySelector("#test-button");
366
+
367
+ var callback = function(resp){
368
+ this.inTransit = false;
369
+ this.handleXhrCallback(resp);
370
+ }.bind(this)
371
+
257
372
  button.addEventListener("click", function(e){
373
+ // console.log(this.action, this.unscopedData);
374
+ // return;
375
+ if (this.inTransit) return;
376
+ if (this.isFormInvalid()) return;
377
+
258
378
  if (this.action !== null){
259
379
  if (this.xhr){
380
+ this.inTransit = true;
381
+
260
382
  if (!!this.scope && this.scope.length > 0){
261
383
  var data = {};
262
384
  data[this.scope] = this.data;
@@ -267,18 +389,19 @@
267
389
  this.$.xhr.request({
268
390
  method: this.method,
269
391
  url: this.action,
270
- body: JSON.stringify(data),
271
- callback: this.handleXhrCallback,
272
- headers: { "Content-type": "application/json", "Accept": "application/json" }
392
+ body: new FormData(form),
393
+ callback: callback
394
+ //headers: { "Content-type": "application/json", "Accept": "application/json" }
273
395
  })
396
+
274
397
  } else {
398
+ this.inTransit = true;
275
399
  form.submit();
276
400
  }
277
401
  }
278
402
  }.bind(this), false)
279
403
 
280
404
  this.appendInputs();
281
-
282
405
  var inputObserver = new ArrayObserver(this.inputList);
283
406
  inputObserver.open(function(splices){
284
407
  this.appendInputs();
@@ -291,6 +414,17 @@
291
414
  }
292
415
  }.bind(this), true)
293
416
 
417
+ var spinner = button.querySelector('form-spinner');
418
+ new PathObserver(this, 'inTransit').open(function(){
419
+ if (this.inTransit){
420
+ spinner.start();
421
+ this.addClass(button, 'in-transit');
422
+ } else {
423
+ spinner.stop();
424
+ this.removeClass(button, 'in-transit');
425
+ }
426
+ }.bind(this));
427
+
294
428
  },
295
429
 
296
430
  get getData() {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polymer-rails-forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Weber
@@ -33,6 +33,7 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - LICENSE
35
35
  - README.md
36
+ - assets/rails-forms/form-spinner.html
36
37
  - assets/rails-forms/gc-paper-decorator.html
37
38
  - assets/rails-forms/pikaday.css
38
39
  - assets/rails-forms/pikaday.js