polymer-rails-forms 0.2.01 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  <link rel="import" href="rails-form-helpers.html" >
2
2
  <link rel="import" href="rails-form-validators.html" >
3
+ <link rel="import" href="rails-form-autocomplete.html" >
3
4
  <link rel="import" href="gc-paper-decorator.html" >
4
5
  <link rel="import" href="form-spinner.html" >
5
6
  <link rel="import" href="../paper-button/paper-button.html" >
@@ -8,70 +9,112 @@
8
9
 
9
10
  <script src='pikaday.js'></script>
10
11
 
12
+ <style>
13
+ /*
14
+ these are attached to the body if scrollTarget is set, so they need to be outside of the shadowDOM
15
+ */
16
+ .pseudo-select {
17
+ opacity: 1;
18
+ display: block;
19
+ transition: opacity 250ms ease-out;
20
+ position: absolute;
21
+ top: 100%;
22
+ width: 106%;
23
+ background-color: white;
24
+ box-shadow: -2px 1px 10px #888;
25
+ z-index: 1;
26
+ margin-top: -10px;
27
+ }
28
+
29
+ .pseudo-select > div {
30
+ min-height: 2em;
31
+ border-bottom: solid 1px;
32
+ padding: 5px 3%;
33
+ position: relative;
34
+ }
35
+
36
+ .pseudo-select > div:hover {
37
+ background-color: #D5DAFF;
38
+ }
39
+
40
+ .pseudo-select > div paper-checkbox {
41
+ float: left;
42
+ margin-right: 15px;
43
+ }
44
+ </style>
45
+
11
46
  <polymer-element name="rails-form">
12
47
  <template>
13
48
  <link rel='stylesheet' href='pikaday.css'>
14
49
  <style>
15
- :host {
16
- display: block;
50
+ #inputs-wrapper {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ flex-direction: row;
54
+ position: relative;
17
55
  }
18
- paper-button {
19
- font-size: 14px;
56
+
57
+ .input, .nest, .list-group, .group, .step{
58
+ flex: 1;
59
+ min-width: 100%;
60
+ max-width: 0;
61
+ position: relative;
62
+ display: inline-block;
63
+ box-sizing: border-box;
64
+ margin-bottom: 0px;
20
65
  }
21
66
 
22
- paper-button core-icon {
23
- margin-right: 5px;
24
- color: #5bc0de;
67
+ .input {
68
+ padding: 0px 10px;
25
69
  }
26
-
27
- #submit-button {
28
- margin-top: 15px;
29
- margin-bottom: 5px;
30
- padding-left: 30px;
31
- padding-right: 30px;
32
- background-color: #5bc0de;
33
- border-color: #46b8da;
34
- color: white;
35
- clear: both;
36
- margin-right: 15px;
70
+
71
+ .input.json {
72
+ padding: 0px;
37
73
  }
38
74
 
39
- #submit-button.in-transit span{
75
+ .input.json .json-wrapper > textarea {
40
76
  display: none;
41
77
  }
42
78
 
43
- #submit-button.in-transit form-spinner {
44
- display: block;
79
+ .nest {
80
+ display: flex;
81
+ flex-wrap: wrap;
45
82
  }
46
83
 
47
- #submit-button form-spinner {
48
- display: none;
84
+ .step:last-of-type {
85
+ margin-bottom: -60px; /* To suck the submit buttom up */
49
86
  }
50
87
 
51
- gc-input-decorator {
52
- text-align: left !important;
88
+ gc-input-decorator.invalid /deep/ .error {
89
+ display: none !important;
53
90
  }
54
91
 
55
- gc-input-decorator .label {
56
- text-align: left !important;
92
+ /* File upload CSS */
93
+ .btn-file-upload input[type=file] {
94
+ position: absolute;
95
+ top: 0;
96
+ left: 0;
97
+ width: 100%;
98
+ height: 100%;
99
+ font-size: 999px;
100
+ text-align: right;
101
+ filter: alpha(opacity=0);
102
+ opacity: 0;
103
+ outline: none;
104
+ background: white;
105
+ cursor: pointer;
106
+ display: block;
107
+ z-index: 1;
57
108
  }
58
109
 
59
- .btn-image-upload {
110
+ .btn-file-upload {
60
111
  position: relative;
61
112
  overflow: hidden;
62
- width: 100%;
63
- height: 59px;
64
- display: table;
65
- }
66
-
67
- .btn-image-upload core-icon {
68
- height: 105px;
69
- width: 105px;
70
- margin: 0 auto;
71
113
  display: block;
72
- color: #666;
114
+
73
115
  }
74
116
 
117
+ /* Image upload CSS */
75
118
  .btn-image-upload input[type=file] {
76
119
  position: absolute;
77
120
  top: 0;
@@ -84,78 +127,78 @@
84
127
  opacity: 0;
85
128
  outline: none;
86
129
  background: white;
87
- cursor: inherit;
130
+ cursor: pointer;
88
131
  display: block;
132
+ z-index: 1;
89
133
  }
90
134
 
91
- .list-group {
92
- width: 100%;
93
- box-sizing: border-box;
135
+ .btn-image-upload {
136
+ position: relative;
137
+ overflow: hidden;
94
138
  display: block;
95
- padding: 9px 0px 0px 10px;
96
- font-size: 14px;
139
+ font-size: 1000%;
140
+ width: 1.5em;
141
+ height: 1em;
142
+ text-align: center;
143
+ margin: 0px auto;
144
+ background-size: contain;
145
+ background-repeat: no-repeat;
146
+ background-position: center;
97
147
  }
98
148
 
99
- .list-group .list-item {
149
+ .btn-image-upload core-icon {
150
+ height: 100%;
100
151
  width: 100%;
152
+ margin: 0 auto;
153
+ display: block;
154
+ color: #666;
155
+ font-size: 10%;
101
156
  }
102
157
 
103
- .list-group .list-item .list-item-display {
104
- border-bottom: solid 1px;
105
- display: flex;
106
- flex-direction: row;
158
+ .has-image .btn-image-upload core-icon {
159
+ display: none;
107
160
  }
108
161
 
109
- .list-group .list-item .list-item-display div {
110
- flex: 1;
111
- cursor: pointer;
162
+ .has-image .btn-image-upload p {
163
+ display: none;
112
164
  }
113
165
 
114
- .list-wrapper {
115
- overflow: auto;
116
- font-size: 0px;
117
- padding-top: 10px;
118
- min-height: 55px;
166
+ .btn-image-upload div {
167
+ height: 75%;
168
+ width: 100%;
119
169
  }
120
170
 
121
- .list-wrapper paper-button {
122
- max-width: 28%;
123
- margin: 0px;
171
+ .btn-image-upload p {
172
+ font-size: 10%;
173
+ margin-top: 0px;
124
174
  }
125
175
 
126
- .list-wrapper paper-button /deep/ .button-content{
127
- -webkit-justify-content: flex-start !important;
128
- justify-content: flex-start !important;
176
+ /* Lists */
177
+
178
+ .list-group > .list-form {
179
+ display: none;
129
180
  }
130
181
 
131
- .list-wrapper .list {
132
- overflow: auto;
133
- position: relative;
134
- float: right;
135
- box-sizing: border-box;
136
- width: 72%;
137
- max-height: 81px;
182
+ .list-group > .list-item {
183
+ display: block;
138
184
  }
139
185
 
140
- .list-wrapper h4 {
141
- padding: 10px 15px;
142
- float: left;
143
- box-sizing: border-box;
144
- width: 25%;
145
- margin: 0;
146
- font-weight: 500;
147
- font-size: 14px;
186
+ .list-group.focused .list-item, .list-group.is-invalid .list-item, .list-group.is-blank > .list-item {
187
+ display: none;
148
188
  }
149
189
 
150
- .list-group .list-item {
190
+ .list-group.focused .list-form, .list-group.is-invalid .list-form, .list-group.is-blank > .list-form {
151
191
  display: block;
152
- list-style-type: none;
153
- margin: 0;
154
- padding: 0 15px;
155
- box-sizing: border-box;
192
+ overflow: hidden;
156
193
  }
157
194
 
158
- .list-group .list-item core-icon {
195
+ .list-group .list-item-display {
196
+ display: flex;
197
+ border-bottom: solid 1px;
198
+ margin-bottom: 5px;
199
+ }
200
+
201
+ .list-group .list-item-display > core-icon {
159
202
  height: 0px;
160
203
  border: solid 1px #A1A1A1;
161
204
  padding: 7px;
@@ -163,25 +206,68 @@
163
206
  border-radius: 8px;
164
207
  color: red;
165
208
  margin-right: 10px;
209
+ cursor: pointer;
166
210
  }
167
211
 
168
- .list-group .list-form {
169
- display: none;
212
+ .list-group .list-item-display > div {
213
+ cursor: pointer;
214
+ flex: 1;
170
215
  }
171
216
 
172
- .list-group.focused .list-item, .list-group.is-invalid .list-item, .list-group.is-blank .list-item {
173
- display: none;
217
+ .nest.addable {
218
+ padding-left: 25%;
174
219
  }
175
220
 
176
- .list-group.focused .list-form, .list-group.is-invalid .list-form, .list-group.is-blank .list-form {
177
- display: block;
178
- overflow: hidden;
221
+ .nest.addable .add-button {
222
+ flex: 1;
223
+ min-width: 25%;
224
+ max-width: 25%;
225
+ margin: 0;
226
+ padding: 0px 1em 0px 0em;
227
+ box-sizing: border-box;
228
+ min-height: 4em;
229
+ margin-left: -25%;
230
+ }
231
+
232
+ .nest.addable .add-button core-icon {
233
+ min-width: 2em;
234
+ min-height: 2em;
235
+
236
+ margin-left: -2.5em;
237
+ display: inline-block;
238
+ position: absolute;
239
+ left: 0;
240
+ color: #5bc0de;
241
+ }
242
+
243
+ .nest.addable .add-button div {
244
+ display: inline-block;
245
+ }
246
+
247
+ .list-group .list-form .list-group .list-item {
248
+ min-width: 100%;
249
+ }
250
+
251
+ .addable .list-form, .addable .list-item {
252
+ min-width: 100%;
253
+ }
254
+
255
+ .list-group .list-form {
256
+
179
257
  }
180
258
 
259
+ .list-group .list-item {
260
+ padding: 10px;
261
+ box-sizing: border-box;
262
+ }
263
+
264
+
265
+ /* Checkboxes */
181
266
  paper-checkbox input[type=checkbox] {
182
267
  display: none;
183
268
  }
184
269
 
270
+ /* Selects */
185
271
  .select-wrapper {
186
272
  position: relative;
187
273
  }
@@ -218,6 +304,13 @@
218
304
  height: 35px;
219
305
  }
220
306
 
307
+ /* Firefox fix */
308
+ .select-wrapper .select-icon svg {
309
+ width: 35px;
310
+ right: 0;
311
+ left: auto;
312
+ }
313
+
221
314
  .select-wrapper .pseudo-select > div {
222
315
  min-height: 2em;
223
316
  border-bottom: solid 1px;
@@ -234,88 +327,103 @@
234
327
  margin-right: 15px;
235
328
  }
236
329
 
330
+ .input.id {
331
+ display: none;
332
+ }
333
+
334
+ /* Autocomplete */
335
+
336
+ .autocomplete-selector {
337
+ position: absolute;
338
+ background-color: white;
339
+ box-shadow: -2px 1px 10px #888;
340
+ z-index: 1;
341
+ padding: 5px;
342
+ border: solid 1px #888;
343
+ box-sizing: border-box;
344
+ margin-top: -11px;
345
+ }
346
+
347
+ .autocomplete-selector .autocomplete-list-item {
348
+ border-bottom: solid 1px #ccc;
349
+ margin-top: 5px;
350
+ padding: 0px 10px 0px 0px;
351
+ }
352
+
353
+ .autocomplete-selector .autocomplete-list-item:last-of-type {
354
+ border-bottom: 0px;
355
+ }
356
+
357
+ .autocomplete-selector .autocomplete-list-item.core-selected {
358
+ background-color: #D5DAFF;
359
+ }
360
+
361
+ .autocomplete-selector .autocomplete-list-item:hover {
362
+ background-color: #D5DAFF;
363
+ }
364
+
365
+ /* Submit Button */
366
+
367
+ #submit-button, .step .next-button, .step .back-button {
368
+ margin-top: 15px;
369
+ margin-bottom: 5px;
370
+ padding-left: 30px;
371
+ padding-right: 30px;
372
+ background-color: #5bc0de;
373
+ border-color: #46b8da;
374
+ color: white;
375
+ clear: both;
376
+ margin-right: 15px;
377
+ }
378
+
379
+ .stepped .step .back-button {
380
+ display: none;
381
+ }
382
+
383
+ .stepped .step ~ .step .back-button {
384
+ display: inline-block;
385
+ }
386
+
387
+ .stepped #submit-button, .stepped .next-button {
388
+ float: right;
389
+ clear: inherit;
390
+ }
391
+
392
+ .stepped .back-button {
393
+ float: left;
394
+ }
395
+
396
+ #submit-button.in-transit span{
397
+ display: none;
398
+ }
399
+
400
+ #submit-button.in-transit form-spinner {
401
+ display: block;
402
+ }
403
+
404
+ #submit-button form-spinner {
405
+ display: none;
406
+ }
237
407
  </style>
238
408
  <!-- TODO: structures should be arrays to preserve order -->
239
409
  <form id="rails_form" _action="{{ action }}" method="{{ method }}" enctype="multipart/form-data">
240
- <template bind="{{ data as data }}">
241
- <template bind="{{ structure as structure }}" bind="{{ scope as scope }}" id="form-group">
242
- <template repeat="{{ key in structure | getKeys }}">
243
- <div class='{{ structure[key] | wrapperClass }} {{ key }} {{ structure[key] | isHidden }}'>
244
- <template if="{{ structure[key] | isNest }}">
245
- <template if="{{ structure[key].allowAdd }}">
246
- <paper-button class='{{ key | scopeToClass }}' on-click="{{ addItem }}"><core-icon icon="add-circle-outline"></core-icon>{{ structure[key].label }}</paper-button>
247
- </template>
248
- <template if="{{ structure[key] | isMultiple }}">
249
- <template if="{{ !structure[key].allowAdd }}">
250
- <template repeat="{{ item in data | scopeNestData(key) | enumerate }}">
251
- <template ref="form-group" bind="{{ structure[key].structure as structure }}" parentContext="{{ key | updateParentContext }}" scope="{{ scope | updateScope(key) | addIndex(item) }}" data="{{ data | scopeDataToIndex(item) }}"></template>
252
- </template>
253
- </template>
254
-
255
- <template if='{{ structure[key].allowAdd }}'>
256
- <template bind="{{ data as data }}">
257
- <div class='list'>
258
- <template repeat="{{ item in data | scopeNestData(key) | enumerate }}">
259
- <div class='list-group {{ item | isEmpty }}'>
260
- <div class='list-item {{ item | listItemClassName }}'>
261
- {{ item | createListItem }}
262
- </div>
263
- <div class='list-form {{ item | listItemClassName }}'>
264
- <template ref="form-group" bind="{{ structure[key].structure as structure }}" parentContext="{{ key | updateParentContext }}" scope="{{ scope | updateScope(key) | addIndex(item) }}" data="{{ data | scopeDataToIndex(item) }}"></template>
265
- </div>
266
- </div>
267
- </template>
268
- </div>
269
- </template>
270
- </template>
271
- </template>
272
-
273
- <template if="{{ structure[key] | isNotMultiple }}">
274
- <template bind="{{ context as context }}">
275
- <template ref="form-group" bind="{{ structure[key].structure as structure }}" parentContext="{{ key | updateParentContext }}" scope="{{ scope | updateScope(key) }}" data="{{ data | scopeNestData(key) }}"></template>
276
- </template>
277
- </template>
278
- </template>
279
-
280
- <template if="{{ structure[key] | isField }}">
281
- <template bind="{{ data as data }}">
282
- <div class="input {{ key | scopeToClass }}" scope="{{ data | scopeFieldData(key) }}">
283
- {{ structure[key] | createInput }}
284
- </div>
285
- </template>
286
- </template>
287
- </div>
288
- </template>
289
- </template>
290
- </template>
410
+ <div id='inputs-wrapper'>
411
+
412
+ </div>
413
+
291
414
  <div class='sumbit-button-wrapper'>
292
415
  <paper-button id='submit-button' raised>
293
416
  <span class='submit-text'>{{ submitText }}</span>
294
- <form-spinner height="1.5" width="1.5"></form-spinner>
417
+ <form-spinner height="1.5" width="1.5" unit='em'></form-spinner>
295
418
  </paper-button>
296
- <!-- <paper-button id='test-button' raised>Test</paper-button> -->
297
419
  <template if="{{ xhr }}">
298
420
  <core-xhr id='xhr'></core-xhr>
299
421
  </template>
300
- </div>
422
+ </div>
423
+
301
424
  </form>
302
425
  </template>
303
426
  <script>
304
- /*
305
- Default Structure Item: {
306
- type: "string",
307
- label: "",
308
- options: {
309
- floatingLabel: true,
310
- unscoped: false,
311
- events: {}
312
- },
313
- attributes: {
314
-
315
- }
316
- }
317
- */
318
-
319
427
  Polymer( Polymer.mixin ({
320
428
  publish: {
321
429
  scope: "",
@@ -328,21 +436,17 @@
328
436
  unscopedData: null
329
437
  },
330
438
 
331
- parentContext: null,
332
- parentStructure: null,
333
- parentData: null,
334
-
439
+ jsonData: null,
335
440
  inTransit: false,
336
-
337
- toArray: function(str){
338
- return [str];
339
- },
441
+ steps: null,
340
442
 
341
443
  created: function(){
342
- this.structure = {};
444
+ this.structure = [];
343
445
  this.inputList = [];
344
- this.indexStore = {};
345
446
 
447
+ this.errors = {};
448
+ this.validatedInputs = {};
449
+
346
450
  var data = this.data === null ? {} : this.data;
347
451
  if (!!this.scope && this.scope.replace(/\s/g, "").length > 0){
348
452
  if (data[this.scope] === void(0)) data[this.scope] = {};
@@ -352,17 +456,14 @@
352
456
  }
353
457
 
354
458
  this.unscopedData = data;
355
- this.unscopedScope = this.scope;
356
- },
357
-
358
- handleXhrCallback: function(){
359
-
459
+ this.jsonData = {};
360
460
  },
361
461
 
362
462
  domReady: function(){
463
+ this.buildForm();
464
+
363
465
  var form = this.shadowRoot.querySelector("form"),
364
- button = this.shadowRoot.querySelector("#submit-button"),
365
- test = this.shadowRoot.querySelector("#test-button");
466
+ button = this.shadowRoot.querySelector("#submit-button");
366
467
 
367
468
  var callback = function(resp){
368
469
  this.inTransit = false;
@@ -372,6 +473,7 @@
372
473
  button.addEventListener("click", function(e){
373
474
  // console.log(this.action, this.unscopedData);
374
475
  // return;
476
+
375
477
  if (this.inTransit) return;
376
478
  if (this.isFormInvalid()) return;
377
479
 
@@ -401,12 +503,6 @@
401
503
  }
402
504
  }.bind(this), false)
403
505
 
404
- this.appendInputs();
405
- var inputObserver = new ArrayObserver(this.inputList);
406
- inputObserver.open(function(splices){
407
- this.appendInputs();
408
- }.bind(this))
409
-
410
506
  form.addEventListener("change", function(e){
411
507
  var group = this.findParent(e.target, ".list-group")
412
508
  if (!!group){
@@ -427,9 +523,260 @@
427
523
 
428
524
  },
429
525
 
430
- get getData() {
431
- return this.data;
526
+ handleXhrCallback: function(){
527
+
528
+ },
529
+
530
+ buildForm: function(){
531
+ var wrapper = this.shadowRoot.querySelector("#inputs-wrapper"),
532
+ self = this;
533
+
534
+ if (!Array.isArray(this.structure)) return;
535
+
536
+ this.buildStructure(this.structure, this.data, wrapper, this.scope);
537
+
538
+ this.steps = wrapper.querySelectorAll(".step.form-step");
539
+ if (this.steps.length > 0){
540
+ this.addClass(this.shadowRoot.querySelector("form"), "stepped");
541
+ this.showStep(0);
542
+
543
+ this.steps[this.steps.length - 1].querySelector(".next-button").style.display = 'none';
544
+ }
545
+ },
546
+
547
+ currentStep: 0,
548
+ showStep: function(index){
549
+ for (var i=0; i<this.steps.length; i++){
550
+ if (i === index){
551
+ this.steps[i].style.display = "";
552
+ } else {
553
+ this.steps[i].style.display = "none";
554
+ }
555
+ }
556
+
557
+ if (index === this.steps.length - 1){
558
+ this.shadowRoot.querySelector("#submit-button").style.display = "";
559
+ } else {
560
+ this.shadowRoot.querySelector("#submit-button").style.display = "none";
561
+ }
562
+ },
563
+
564
+ nextStep: function(){
565
+ if (this.currentStep < this.steps.length - 1){
566
+ this.currentStep += 1;
567
+ this.showStep(this.currentStep);
568
+ }
569
+ },
570
+
571
+ previousStep: function(){
572
+ if (this.currentStep > 0){
573
+ this.currentStep -= 1;
574
+ this.showStep(this.currentStep);
575
+ }
576
+ },
577
+
578
+ updateStructureDOM: function(splices, structure, data, wrapper, scope, dom, domKeys){
579
+ var self = this;
580
+
581
+ for (var i=0; i<splices.length; i++){
582
+ /* delete removed children */
583
+ for (var j=0; j<splices[i].removed.length; j++){
584
+ var key = splices[i].removed[j].key,
585
+ element = domKeys[key];
586
+
587
+ element.parentNode.removeChild(element),
588
+ dom.splice(dom.indexOf(element), 1);
589
+
590
+ delete dom[key];
591
+ delete data[key];
592
+ }
593
+
594
+ /* add new children */
595
+ for (var j=0; j<splices[i].addedCount; j++){
596
+ var index = splices[i].index + j,
597
+ previousElement = dom[index -1],
598
+ field = self.createField(structure[index], data, scope);
599
+
600
+ if (!!previousElement){
601
+ wrapper.appendChild(field);
602
+ } else {
603
+ previousElement.parentNode.insertBefore(field, previousElement.nextSibling);
604
+ }
605
+
606
+ dom.push(field);
607
+ domKeys[structure[index].key] = field;
608
+ }
609
+ }
610
+ },
611
+
612
+ buildStructure: function(structure, data, wrapper, scope){
613
+ /* handling DOM insertion and removal */
614
+ var dom = [],
615
+ domKeys = {},
616
+ self = this;
617
+
618
+ new ArrayObserver(structure).open(function(splices){
619
+ self.updateStructureDOM(splices, structure, data, wrapper, scope, dom, domKeys);
620
+ self.fire("structure-changed", { ref: this });
621
+ });
622
+
623
+ /* actually building the form */
624
+ for (var i=0; i<structure.length; i++){
625
+ var field = self.createField(structure[i], data, scope)
626
+ wrapper.appendChild(field);
627
+
628
+ dom.push(field);
629
+ domKeys[structure[i].key] = field;
630
+
631
+ if ((structure[i].type === "nest") && structure[i].multiple){
632
+ var watched_data = !!structure[i].unscoped ? this.unscopedData[structure[i].key] : data[structure[i].key];
633
+ new ArrayObserver(watched_data).open( (function(s, f){
634
+ return function(splices){
635
+ var nField = self.createField(s, data, scope);
636
+ f.parentNode.insertBefore(nField, f);
637
+ f.parentNode.removeChild(f);
638
+ f = nField;
639
+
640
+ self.fire("data-inserted", {ref: this});
641
+ };
642
+ })(structure[i], field) );
643
+ }
644
+ }
645
+
646
+ return wrapper;
647
+ },
648
+
649
+ createField: function(structure, data, scope){
650
+ var key = structure.key,
651
+ self = this;
652
+
653
+ if (structure.type === "group"){
654
+ var wrap = this.createWithAttributes("div", {"class": ["group", structure.key, structure.additionalClasses].join(" ")}),
655
+ contents = this.buildStructure(structure.structure, data, wrap, scope);
656
+
657
+ return wrap;
658
+ } else if (structure.type === "step") {
659
+ var wrap = this.createWithAttributes("div", {"class": ["step form-step", structure.key, structure.additionalClasses].join(" ")}),
660
+ next_button = this.createWithAttributes("paper-button", { "class": "next-button", text: (structure.nextText || "Next"), raised: true }),
661
+ back_button = this.createWithAttributes("paper-button", { "class": "back-button", text: (structure.backText || "Back"), raised: true }),
662
+ contents = this.buildStructure(structure.structure, data, wrap, scope);
663
+
664
+ next_button.addEventListener("click", function(){ this.nextStep(); }.bind(this), false);
665
+ back_button.addEventListener("click", function(){ this.previousStep(); }.bind(this), false);
666
+
667
+ wrap.appendChild(back_button);
668
+ wrap.appendChild(next_button);
669
+
670
+ return wrap;
671
+ } else if (structure.type === "nest"){
672
+ var wrap = self.createWithAttributes("div", {"class": "nest " + key}),
673
+ nested_data = self.scopeNestData(structure, key, data),
674
+ nested_scope = self.updateScope(structure, key, scope);
675
+
676
+ if (structure.allowAdd){
677
+ var add_button = self.createWithAttributes("paper-button", {"class": "add-button " + self.scopeToClass(structure, key, scope)}),
678
+ add_icon = self.createWithAttributes("core-icon", {icon: "add-circle-outline"}),
679
+ add_button_text = self.createWithAttributes("div", {text: structure.label || key.replace(/_/g)});
680
+
681
+ add_button.appendChild(add_icon);
682
+ add_button.appendChild(add_button_text);
683
+
684
+ wrap.appendChild(add_button);
685
+ this.addClass(wrap, 'addable');
686
+
687
+ add_button.addEventListener('click', function(){
688
+ var items = wrap.querySelectorAll('.list-group'),
689
+ blank = false;
690
+
691
+ for (var i=0; i<items.length; i++){
692
+ if (self.checkForBlank(items[i])){
693
+ blank = true;
694
+ break;
695
+ }
696
+ }
697
+
698
+ if (!blank) nested_data.unshift({});
699
+ }, false);
700
+ }
701
+
702
+ if (structure.multiple){
703
+
704
+ for (var i=0; i<nested_data.length; i++){
705
+ if (structure.allowAdd){
706
+ var item_wrapper = self.createWithAttributes("div", {"class": "list-group"}),
707
+ list_item = self.createWithAttributes("div", {"class": "list-item"}),
708
+ list_form = self.createWithAttributes("div", {"class": "list-form"});
709
+
710
+ list_item.appendChild( self.createListItem(nested_data[i], structure, nested_data) );
711
+ item_wrapper.appendChild(list_item);
712
+ item_wrapper.appendChild(list_form);
713
+
714
+ self.buildStructure(structure.structure, nested_data[i], list_form, self.addIndex(nested_scope, i));
715
+ } else {
716
+ var item_wrapper = self.createWithAttributes("div", {"class": "list-group"});
717
+ self.buildStructure(structure.structure, nested_data[i], item_wrapper, self.addIndex(nested_scope, i));
718
+ }
719
+
720
+ wrap.appendChild(item_wrapper);
721
+ self.checkForBlank(item_wrapper);
722
+ }
723
+ } else {
724
+ self.buildStructure(structure.structure, nested_data, wrap, nested_scope);
725
+ }
726
+
727
+ return wrap;
728
+
729
+ } else {
730
+ //TODO: set defaults here
731
+ var options = structure.options || {},
732
+ fieldData = self.scopeFieldData((options.unscoped ? self.unscopedData : data), structure, key),
733
+ wrap = self.createWithAttributes("div", {"class": "input " + structure.type + " " + key}),
734
+ inputElement = self.createInput(structure, fieldData, key, scope);
735
+
736
+ wrap.appendChild(inputElement);
737
+ return wrap;
738
+ }
739
+ },
740
+
741
+ getField: function(key){
742
+ var key = key.split("."),
743
+ structure = this.structure;
744
+ for (var i=0; i<key.length; i++){
745
+ if (!structure) return null;
746
+ var field = structure.filter(function(item){ return item.key === key[i] })[0];
747
+ if (!field) return null;
748
+
749
+ structure = field.structure;
750
+ }
751
+
752
+ return field;
753
+ },
754
+
755
+ loadData: function(data){
756
+ //TODO: deal with unscoped data somehow
757
+ var recursive = function(d1, d2, structure){
758
+ if (Array.isArray(d1) && Array.isArray(d2)){
759
+ for (var i=0; i<d2.length; i++){
760
+ if (!d1[i]) d1.push({});
761
+ recursive(d1[i], d2[i], structure);
762
+ }
763
+ } else {
764
+ for (var i=0; i<structure.length; i++){
765
+ if (d2[structure[i].key] !== void(0)){
766
+ if (structure[i].type === 'nest'){
767
+ recursive(d1[structure[i].key], d2[structure[i].key], structure[i].structure);
768
+ } else {
769
+ d1[structure[i].key] = d2[structure[i].key];
770
+ }
771
+ }
772
+ }
773
+ }
774
+ }
775
+
776
+ this.async(function(){
777
+ recursive(this.data, data, this.structure);
778
+ });
432
779
  }
433
- }, FormHelpers, Validation));
780
+ }, FormHelpers, Validation, Autocomplete));
434
781
  </script>
435
782
  </polymer-element>