polymer-rails-forms 0.2.01 → 0.3.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.
@@ -2,16 +2,12 @@
2
2
  /* TODO clean this up */
3
3
  var FormHelpers = {
4
4
  /* Utility things */
5
- log: function(o){
6
- console.log("Data", o)
7
- },
8
-
9
5
  capitalize: function(str){
10
- if (str && str.length > 1){
11
- return str.split(" ").map( function(s){ return s[0].toUpperCase() + s.substr(1) } ).join(" ");
12
- } else {
13
- return "";
14
- }
6
+ if (str && str.length > 1){
7
+ return str.split(" ").map( function(s){ return s[0].toUpperCase() + s.substr(1) } ).join(" ");
8
+ } else {
9
+ return "";
10
+ }
15
11
  },
16
12
 
17
13
  findParent: function(el, selector){
@@ -52,132 +48,6 @@
52
48
  }
53
49
  },
54
50
 
55
- indexStore: {},
56
- genIndexStoreKey: function(){
57
- var key = "_" + ((Math.random() * 1000000) | 0);
58
- while(this.indexStore[key] !== void(0)){
59
- key = "_" + ((Math.random() * 1000000) | 0);
60
- }
61
-
62
- return key;
63
- },
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
-
71
- addToIndexStore: function(obj, index){
72
- var key = this.genIndexStoreKey();
73
- this.indexStore[key] = {obj: obj, index: index}
74
- },
75
-
76
- removeFromIndexStore: function(obj){
77
- for (var x in this.indexStore){
78
- if (this.indexStore[x].obj === obj) delete this.indexStore[x];
79
- }
80
- },
81
-
82
- updateIndex: function(obj, index){
83
- for (var x in this.indexStore){
84
- if (this.indexStore[x].obj === obj) this.indexStore[x].index = index;
85
- }
86
- },
87
-
88
- enumerate: function(obj){
89
- //TODO: keygen should check for uniquness
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);
100
- }
101
-
102
- return obj
103
- },
104
-
105
- /* Type Checks */
106
-
107
- getKeys: function(o){
108
- return Object.keys(o);
109
- },
110
-
111
- isArray: function(v){
112
- return Array.isArray(v);
113
- },
114
-
115
- isNotArray: function(v){
116
- return !Array.isArray(v);
117
- },
118
-
119
- isNest: function(v){
120
- return v.type === "nest";
121
- },
122
-
123
- isField: function(v){
124
- return v.type !== "nest";
125
- },
126
-
127
- isMultiple: function(s){
128
- return s.multiple;
129
- },
130
-
131
- isNotMultiple: function(s){
132
- return !s.multiple
133
- },
134
-
135
- isHidden: function(o){
136
- return o.type === 'hidden' ? "hidden" : "";
137
- },
138
-
139
- isEmpty: function(o){
140
- var blank = true;
141
- for (var x in this.structure[this.key].structure){
142
- if (!blank) break;
143
- if (this.structure[this.key].structure[x].type !== 'nest'){
144
- if (this.structure[this.key].structure[x].type === 'json'){
145
- for (var y in o[x]){
146
- if (typeof o[x][y] !== 'object'){
147
- if (!(o[x][y] === null || o[x][y].length === 0)){
148
- blank = false;
149
- break;
150
- }
151
- }
152
- }
153
- } else {
154
- if (!(o[x] === null || o[x].length === 0)){
155
- blank = false;
156
- break;
157
- }
158
- }
159
- }
160
- }
161
-
162
- if (!blank){
163
- return "";
164
- } else {
165
- return "is-blank";
166
- }
167
- },
168
-
169
- exists: function(o, key){
170
- return o[key] !== void(0)
171
- },
172
-
173
- notExists: function(o, key){
174
- return o[key] === void (0)
175
- },
176
-
177
- displayableField: function(field){
178
- return ["string"].indexOf(field.type) !== -1;
179
- },
180
-
181
51
  /* Scope things */
182
52
  wrapperClass: function(structure){
183
53
  if (structure.type === "nest"){
@@ -191,96 +61,54 @@
191
61
  }
192
62
  },
193
63
 
194
- addIndex: function(scope, obj){
195
- var index = this.getIndex(obj);
196
-
197
- this.scope = this.structure[this.key].multiple ? scope + "[" + index + "]" : scope;
198
- return this.scope;
64
+ addIndex: function(scope, index){
65
+ return scope + "[" + index + "]";
199
66
  },
200
67
 
201
- addItem: function(e, d, t){
202
- var parent = this.findParent(t, ".list-wrapper"),
203
- items = parent.querySelectorAll(".list-form"),
204
- invalid = parent.querySelectorAll(".input-wrapper.is-invalid input")[0];
205
-
206
- for (var i=0; i<items.length; i++){
207
- var inputs = items[i].querySelectorAll("input"),
208
- blank = true;
209
-
210
- for (var j=0; j<inputs.length; j++){
211
- if (inputs[j].value.replace(/\s/g, "").length !== 0){ blank = false; break; }
212
- }
213
-
214
- if (blank){
215
- invalid = inputs[0];
216
- break;
217
- }
218
- }
219
-
220
- if (!invalid){
221
- var data = this.classToData(t.className),
222
- newData = {};
223
-
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);
230
- data.push(newData)
231
- } else {
232
- invalid.focus();
233
- }
68
+ updateFieldScope: function(structure, key, scope){
69
+ if (scope === null) return null;
70
+ return structure.options.unscoped ? key : scope + "[" + key + "]"
234
71
  },
235
72
 
236
- updateScope: function(key){
237
- var scope = this.structure[this.key].unscoped ? this.key : this.scope + "[" + this.key + "_attributes]";
238
-
239
- this.scope = scope;
240
- return this.scope;
73
+ updateScope: function(structure, key, scope){
74
+ if (scope === null) return null;
75
+
76
+ scope = structure.unscoped ? key : scope + "[" + key + "_attributes]";
77
+ return scope;
241
78
  },
242
79
 
243
- scopeFieldData: function(data, key){
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;
80
+ scopeFieldData: function(data, structure, key){
81
+ //data = this.structure[this.key].unscoped ? this.unscopedData : data;
82
+ if (structure.type !== 'html'){
83
+ if (data[key] === void(0)) data[key] = structure.value || null;
248
84
  return data;
249
85
  }
250
86
  },
251
87
 
252
- scopeNestData: function(data, key){
253
- data = this.structure[this.key].unscoped ? this.unscopedData : data;
88
+ scopeNestData: function(structure, key, data){
89
+ data = structure.unscoped ? this.unscopedData : data;
254
90
  //console.log("nest", key, data[key]);
255
- if (this.structure[key].multiple){
256
- if (data[key] === void(0)){
257
- if (this.structure[key].allowAdd) {
91
+ if (structure.multiple){
92
+ if (data[key] === void(0)){
93
+ if (structure.allowAdd) {
258
94
  data[key] = [];
259
95
  } else {
260
96
  data[key] = [{}];
261
97
  }
262
98
  }
263
99
 
264
- this.data = data[key];
265
- } else {
266
- if (data[key] === void(0)) data[key] = {};
267
- this.data = data[key];
268
- }
269
-
270
- return this.data;
271
- },
100
+ } else {
101
+ if (data[key] === void(0)) data[key] = {};
102
+ }
272
103
 
273
- updateParentContext: function(key){
274
- this.parentContext = key;
275
- this.parentStructure = this.structure;
276
- this.parentData = this.data;
104
+ return data[key];
277
105
  },
278
106
 
279
107
  scopeDataToIndex: function(data, obj){
280
108
  var index = this.getIndex(obj);
281
109
 
282
110
  this.data = this.data[index];
283
- return this.data
111
+ return this.data
284
112
  },
285
113
 
286
114
  listItemClassName: function(obj){
@@ -349,25 +177,25 @@
349
177
  },
350
178
 
351
179
  /* Creating Element */
352
- createListItem: function(obj){
180
+ //TODO: Wow, this seems like it could be way simpler.
181
+ createListItem: function(obj, structure, data){
353
182
  var className = this.listItemClassName(obj),
354
- isEmpty = Object.keys(obj).length === 0,
355
183
  container = this.createWithAttributes("div", {'class': "list-item-display"});
356
-
357
- if (this.structure[this.key].display_fields){
358
- for (var i=0; i<this.structure[this.key].display_fields.length; i++){
359
- var field = this.structure[this.key].display_fields[i],
184
+
185
+ if (structure.display_fields){
186
+ for (var i=0; i<structure.display_fields.length; i++){
187
+ var field = structure.display_fields[i],
360
188
  baseField = field.split(".")[0],
361
189
  nameField = field.split(".")[field.split(".").length - 1]
362
190
 
363
191
  var el = document.createElement("div"),
364
192
  label = this.createWithAttributes("span", { text: this.capitalize(nameField.replace(/_/g, " ")) + ": " });
365
-
366
-
193
+
367
194
  var value = document.createElement("span"),
368
- val = document.createTextNode("");
195
+ val = document.createTextNode(""),
196
+ row = structure.structure.filter(function(item){ return item.key === baseField })[0];
369
197
 
370
- if (this.structure[this.key].structure[field.split(".")[0]].type === "json"){
198
+ if (row.type === "json"){
371
199
  var struct = field.split("."),
372
200
  o = obj;
373
201
 
@@ -392,38 +220,23 @@
392
220
  container.appendChild(el);
393
221
  }
394
222
  } else {
395
- container.appendChild(this.createWithAttributes("div", {text: "item " + (this.getIndex(obj) + 1)}))
223
+ container.appendChild(this.createWithAttributes("div", {text: "item " + (data.indexOf(obj) + 1) }))
396
224
  }
397
225
 
398
226
  var remove_button = this.createWithAttributes("core-icon", {icon: 'clear'});
399
227
  remove_button.addEventListener("click", function(e){
400
228
  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
-
229
+ data.splice(data.indexOf(obj), 1);
416
230
  }.bind(this), false)
417
231
 
418
232
  container.appendChild(remove_button);
419
233
 
420
234
  var self = this;
421
- this.inputList.push({ element: container, container: ".list-item." + className, events: {} })
422
-
235
+
423
236
  container.addEventListener("click", function(e){
424
237
  var stop = function(e){ e.stopPropagation(); }
425
238
  group = self.findParent(container, ".list-group"),
426
- first_input = group.querySelector(".input-wrapper gc-input-decorator");
239
+ first_input = group.querySelector(".input gc-input-decorator");
427
240
 
428
241
  stop(e);
429
242
  var alreadyFocued = self.shadowRoot.querySelectorAll(".list-group.focused");
@@ -432,9 +245,9 @@
432
245
  }
433
246
 
434
247
  first_input.focused = true;
435
- setTimeout(function(){
248
+ self.async(function(){
436
249
  first_input.querySelector("input").focus();
437
- }, 100)
250
+ });
438
251
 
439
252
  self.addClass(group, "focused");
440
253
  group.addEventListener("click", stop, false);
@@ -444,10 +257,14 @@
444
257
  self.removeClass(group, "focused");
445
258
  }, false)
446
259
  }, false)
260
+
261
+ return container;
447
262
  },
448
263
 
449
- createInput: function(structure){
450
- var default_structure = {
264
+ createInput: function(structure, data, key, scope){
265
+ data = data || this.data;
266
+
267
+ var default_structure = {
451
268
  type: "string",
452
269
  label: "",
453
270
  options: {
@@ -474,62 +291,26 @@
474
291
  }
475
292
 
476
293
  setOptions(structure, default_structure);
477
- var el = this.getElement(structure),
478
- obj = { element: el, container: "." + this.scopeToClass(this.key), events: structure.options.events };
479
-
480
- this.inputList.push(obj)
294
+
295
+ return this.getElement(structure, data, key, scope);
296
+ //this.inputList.push(obj)
481
297
  },
482
298
 
483
- scopeToClass: function(key){
484
- if (this.structure[this.key].unscoped){
299
+ scopeToClass: function(structure, key, scope){
300
+ if (scope === null) return key;
301
+
302
+ if (structure.unscoped){
485
303
  var className = key
486
304
  } else {
487
- var className = this.scope.replace(/\[/g, "_").replace(/\]/g, "") + "_" + key;
488
- }
489
- return className;
490
- },
491
-
492
- classToData: function(cn){
493
- var self = this,
494
- data = this.unscopedData;
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
-
504
- function nest(str){
505
- var p = str.split("_attributes_");
506
- if (p.length > 1){
507
- data = data[p[0]]
508
- cn = p.slice(1).join("_attributes_");
509
- return true;
510
- } else {
511
- return false
512
- }
305
+ var className = scope.replace(/\[/g, "_").replace(/\]/g, "") + "_" + key;
513
306
  }
514
-
515
- function arr(str){
516
- var p = str.split("_");
517
- if (p.length > 1 && !isNaN(p[0])){
518
- data = data[p[0]];
519
- cn = p.slice(1).join("_");
520
- return true;
521
- } else {
522
- return false
523
- }
524
- }
525
-
526
- while (nest(cn) || arr(cn)){ }
527
- return cn.length > 0 ? data[cn] : data;
307
+ return className;
528
308
  },
529
309
 
530
310
  checkForBlank: function(group){
531
- var inputs = group.querySelectorAll("input"),
532
- blank = true;
311
+ var inputs = group.querySelectorAll("input[type=text], textarea, select"),
312
+ blank = true,
313
+ single = true;
533
314
 
534
315
  for (var i=0; i<inputs.length; i++){
535
316
  if (inputs[i].value.replace(/\s/g, "").length !== 0){
@@ -539,59 +320,59 @@
539
320
  }
540
321
 
541
322
  if (blank){
542
- this.addClass(group, 'is-blank');
323
+ this.addClass(this.findParent(group, ".input"), 'is-blank');
324
+ return true;
543
325
  } else {
544
- this.removeClass(group, 'is-blank');
326
+ this.removeClass(this.findParent(group, ".input"), 'is-blank');
327
+ return false;
545
328
  }
546
329
  },
547
330
 
548
- updateFieldScope: function(){
549
- return this.structure[this.key].options.unscoped ? this.key : this.scope + "[" + this.key + "]"
550
- },
331
+ getElement: function(structure, data, key, scope){
332
+ data = data || this.data;
551
333
 
552
- getElement: function(structure){
553
334
  var inputs = {
554
335
  string: function(){
555
- return this.createBasicInput("text");
336
+ return this.createBasicInput("text", data, structure, key, scope);
556
337
  }.bind(this),
557
338
 
558
339
  email: function(){
559
- return this.createBasicInput("text");
340
+ return this.createBasicInput("text", data, structure, key, scope);
560
341
  },
561
342
 
562
343
  hidden: function(){
563
- return this.createBasicInput("hidden");
344
+ return this.createBasicInput("hidden", data, structure, key, scope);
564
345
  }.bind(this),
565
346
 
566
347
  json: function(){
567
- return this.createJSONField(structure.fields);
348
+ return this.createJSONField(structure.fields, data, structure, key, scope);
568
349
  }.bind(this),
569
350
 
570
351
  password: function(){
571
- return this.createBasicInput("password");
352
+ return this.createBasicInput("password", data, structure, key, scope);
572
353
  }.bind(this),
573
354
 
574
355
  integer: function(){
575
- return this.createBasicInput("text");
356
+ return this.createBasicInput("text", data, structure, key, scope);
576
357
  }.bind(this),
577
358
 
578
359
  url: function(){
579
- return this.createBasicInput("text");
360
+ return this.createBasicInput("text", data, structure, key, scope);
580
361
  }.bind(this),
581
362
 
582
363
  textarea: function(){
583
- return this.createBasicInput("textarea")
364
+ return this.createBasicInput("textarea", data, structure, key, scope)
584
365
  }.bind(this),
585
366
 
586
367
  checkbox: function(){
587
368
  var box = document.createElement("paper-checkbox"),
588
- input = this.createWithAttributes('input', {type: "checkbox", name: this.updateFieldScope()});
369
+ input = this.createWithAttributes('input', {type: "checkbox", name: this.updateFieldScope(structure, key, scope)});
589
370
 
590
- box.label = this.capitalize(this.structure[this.key].label || this.key);
371
+ box.label = this.capitalize(structure.label || key);
591
372
  box.appendChild(input);
592
373
 
593
374
  box.addEventListener("change", function(e){
594
- this.data[this.key] = e.target.checked ? 1 : 0;
375
+ data[key] = e.target.checked ? 1 : 0;
595
376
  input.checked = e.target.checked;
596
377
  }.bind(this), false)
597
378
 
@@ -600,98 +381,162 @@
600
381
  }.bind(this),
601
382
 
602
383
  date: function(){
603
- var wrapper = this.createBasicInput("text"),
604
- input = wrapper.querySelector("input");
384
+ var wrapper = this.createWithAttributes("div", {"class": "date-wrapper"}),
385
+ decorator = this.createWithAttributes("gc-input-decorator", { label: this.capitalize(structure.label || key.replace(/_/g, " ")), floatingLabel: structure.options.floatingLabel}),
386
+ visibleInput = document.createElement("input"),
387
+ input = this.createWithAttributes("input", {type: "hidden", name: this.updateFieldScope(structure, key, scope) })
388
+
389
+ decorator.appendChild(visibleInput);
390
+ wrapper.appendChild(decorator);
391
+ wrapper.appendChild(input);
605
392
 
606
- var picker = new Pikaday({ field: input, bound: false });
393
+ var picker = new Pikaday({ field: visibleInput, bound: false, onSelect: function(){
394
+ data[key] = picker._d.getFullYear() + "-" + (picker._d.getMonth() + 1) + "-" + picker._d.getDate();
395
+ }.bind(this) });
607
396
 
608
- input.addEventListener("focus", function(){
609
- picker.show();
610
- });
397
+ visibleInput.addEventListener("focus", function(){ picker.show(); });
398
+ new PathObserver(data, key).open(function(){ input.value = data[key]; });
611
399
 
612
400
  return wrapper;
613
401
  }.bind(this),
614
402
 
615
403
  select: function(){
616
- var structure = this.structure[this.key],
617
- values = structure.values;
404
+ var values = structure.values;
618
405
 
619
406
  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()}),
407
+ decorator = this.createWithAttributes("gc-input-decorator", { label: this.capitalize(structure.label || key), floatingLabel: structure.options.floatingLabel}),
408
+ secretInput = this.createWithAttributes('input', {type: 'hidden', name: this.updateFieldScope(structure, key, scope)}),
622
409
  input = this.createWithAttributes("input", { type: "text", name: "", "class": "pseudo-select-input", disabled: true }),
623
410
  select = this.createWithAttributes("core-selector", {valueattr: "val", "class": "pseudo-select", multi: (!!structure.multi) });
624
411
 
625
- secretInput.bind('value', new PathObserver(this.data, this.key));
626
- select.bind('selected', new PathObserver(this.data, this.key));
412
+ //secretInput.bind('value', new PathObserver(data, key));
413
+ //select.bind('selected', new PathObserver(data, key));
414
+ new PathObserver(data, key).open(function(){
415
+ secretInput.value = !!structure.multi ? JSON.stringify(data[key]) : data[key];
416
+ select.selected = data[key];
417
+ })
627
418
 
628
419
  if (!structure.blank){
629
- this.data[this.key] = values.length > 0 ? values[0][0] : "";
420
+ data[key] = values.length > 0 ? values[0][0] : (!!structure.multi ? [] : "");
630
421
  input.value = values.length > 0 ? values[0][1] : "";
631
422
  }
632
423
  var self = this;
633
424
 
634
- select.addEventListener("click", function(e){
635
- if (!!self.structure[self.key].multi) e.stopPropagation();
636
- }, false);
425
+ if (!structure.opened){
426
+
427
+ select.addEventListener("click", function(e){
428
+ if (!!structure.multi) e.stopPropagation();
429
+ }, false);
637
430
 
638
- decorator.addEventListener('click', function(e){
639
- if (self.hasClass(wrapper, 'opened')) return;
431
+ decorator.addEventListener('click', function(e){
432
+ if (self.hasClass(wrapper, 'opened')) return;
640
433
 
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);
434
+ e.stopPropagation();
435
+ decorator.updateLabelVisibility("down");
436
+
437
+ if (!structure.scrollTarget){
438
+ self.addClass(wrapper, 'opened');
439
+ } else {
440
+ var scrollTarget = document.querySelector(structure.scrollTarget);
441
+
442
+ if (scrollTarget === document.body){
443
+ var coords = wrapper.getBoundingClientRect();
444
+ coords.top = coords.top + window.pageYOffset + wrapper.offsetHeight;
445
+ coords.left = coords.left + window.pageXOffset;
446
+ } else {
447
+ var parentCoords = scrollTarget.tagName.indexOf("-") !== -1 ? scrollTarget.scroller.getBoundingClientRect() : scrollTarget.getBoundingClientRect(),
448
+ wrapperCoords = wrapper.getBoundingClientRect(),
449
+ scrollTop = scrollTarget.tagName.indexOf("-") !== -1 ? scrollTarget.scroller.scrollTop : scrollTarget.scrollTop;
450
+
451
+ var coords = {
452
+ width: wrapperCoords.width,
453
+ height: wrapperCoords.height,
454
+ top: wrapperCoords.top - parentCoords.top + scrollTop + wrapperCoords.height,
455
+ left: wrapperCoords.left - parentCoords.left + scrollTarget.scrollLeft
456
+ }
457
+ }
458
+
459
+ select.style.width = coords.width + "px";
460
+ select.style.top = (coords.top ) + "px";
461
+ select.style.left = (coords.left) + "px";
462
+
463
+ scrollTarget.appendChild(select);
464
+ }
465
+
466
+ window.addEventListener('click', function(){
467
+ if (!structure.scrollTarget){
468
+ self.removeClass(wrapper, 'opened');
469
+ } else {
470
+ wrapper.appendChild(select);
471
+ }
472
+
473
+ window.removeEventListener('click', arguments.callee);
474
+ }, false);
648
475
  }, false);
649
- }, false);
476
+ } else {
477
+ self.addClass(wrapper, 'opened');
478
+ decorator.updateLabelVisibility("down");
479
+ }
650
480
 
651
481
  select.addEventListener('core-select', function(){
652
- if (!self.structure[self.key].multi) self.removeClass(wrapper, 'opened');
482
+ if (!structure.multi) self.removeClass(wrapper, 'opened');
653
483
  decorator.updateLabelVisibility(select.selected ? select.selected : "");
654
484
 
655
485
  self.async(function(){
656
486
  if (!!structure.multi){
657
487
  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') }) );
488
+ data[key] = select.selectedItem.map(function(s){ return s.getAttribute('val') });
659
489
  } else {
660
490
  input.value = select === null ? "" : select.selectedItem.textContent;
661
- secretInput.value = select.selectedItem.getAttribute('val');
491
+ data[key] = select.selectedItem.getAttribute('val');
662
492
  }
663
493
  });
664
494
  }, false);
665
495
 
666
496
  var build_options = function(){
497
+ var keys = {};
498
+
667
499
  while (select.firstChild) { select.removeChild(select.firstChild) };
668
- if (structure.blank) select.appendChild(this.createWithAttributes("div", {val: "", text: "" }))
500
+ if (!!structure.blank) select.appendChild(this.createWithAttributes("div", {val: "huh", text: (typeof structure.blank === "string" ? structure.blank : "") }))
669
501
  for (var i=0; i<structure.values.length; i++){
670
502
  var div = self.createWithAttributes("div", {val: structure.values[i][0], text: structure.values[i][1] });
671
503
 
672
504
  if (!!structure.multi){
673
505
  var checkbox = this.createWithAttributes("paper-checkbox", {"class": "select-check"});
674
506
  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
507
  }
681
508
  select.appendChild(div);
682
509
  }
683
510
 
684
511
  var nil = structure.values.length === 0 ? self.addClass(wrapper, 'is-blank') : self.removeClass(wrapper, 'is-blank');
685
512
  nil = structure.values.length === 1 ? self.addClass(wrapper, 'is-single') : self.removeClass(wrapper, 'is-single');
513
+
514
+ //if (!structure.blank && values.length > 0) select.selected = values[0][0];
686
515
  }.bind(this);
687
516
 
688
517
  build_options();
689
- new PathObserver(structure, 'values').open(function(){
518
+ new ArrayObserver(structure.values).open(function(){
690
519
  build_options();
691
520
  })
692
521
 
522
+ //Binding the checkboxes state to whether or not they're selected
523
+ if (!!structure.multi){
524
+ new ArrayObserver(select.selection).open(function(changed){
525
+ for (var j=0; j<changed.length; j++){
526
+ var changes = changed[j];
527
+ for (var i=0; i<changes.addedCount; i++){
528
+ select.selectedItem[changes.index + i].querySelector("paper-checkbox").checked = true;
529
+ }
530
+
531
+ for (var i=0; i<changes.removed.length; i++){
532
+ changes.removed[i].querySelector("paper-checkbox").checked = false;
533
+ }
534
+ }
535
+ })
536
+ }
537
+
693
538
  new PathObserver(structure, 'label').open(function(){
694
- decorator.label = this.capitalize(this.structure[this.key].label || this.key)
539
+ decorator.label = this.capitalize(structure.label || key)
695
540
  }.bind(this));
696
541
 
697
542
  wrapper.appendChild(decorator);
@@ -706,14 +551,16 @@
706
551
  var wrapper = document.createElement("gc-input-decorator"),
707
552
  input = document.createElement( "input" );
708
553
  //google-location-input
709
- if (!this.data[this.key]) this.data[this.key] = { value: null, lat: null, lng: null }
554
+ if (!data[key]) data[key] = { value: null, lat: null, lng: null }
710
555
 
711
- wrapper.label = this.capitalize(this.structure[this.key].label || this.key);
712
- wrapper.floatingLabel = this.structure[this.key].options.floatingLabel;
556
+ wrapper.label = this.capitalize(structure.label || key);
557
+ wrapper.floatingLabel = structure.options.floatingLabel;
713
558
 
714
559
  input.type = "text";
715
- input.name = this.updateFieldScope();
716
- input.bind('value', new PathObserver(this.data[this.key], "value"))
560
+ input.name = this.updateFieldScope(structure, key, scope);
561
+ input.bind('value', new PathObserver(data[key], "value"))
562
+
563
+ this.addValidation('location', input, structure, key, data);
717
564
 
718
565
  var autocomplete = new google.maps.places.Autocomplete(
719
566
  input, { types: ['(cities)'] }
@@ -723,12 +570,12 @@
723
570
  google.maps.event.addListener(autocomplete, 'place_changed', function () {
724
571
  var place = autocomplete.getPlace();
725
572
 
726
- self.data[self.key].value = input.value;
727
- self.data[self.key].lat = place.geometry.location.lat();
728
- self.data[self.key].lng = place.geometry.location.lng();
573
+ data[key].value = input.value;
574
+ data[key].lat = place.geometry.location.lat();
575
+ data[key].lng = place.geometry.location.lng();
729
576
 
730
- if (!!self.structure[self.key].events.select){
731
- self.structure[self.key].events.select(self.data[self.key])
577
+ if (!!structure.options.events.select){
578
+ structure.options.events.select(data[key])
732
579
  }
733
580
  })
734
581
 
@@ -738,7 +585,7 @@
738
585
 
739
586
  image: function(){
740
587
  var wrapper = this.createWithAttributes("span", { "class": "btn-image-upload" }),
741
- input = this.createWithAttributes("input", { type: "file", name: this.updateFieldScope() }),
588
+ input = this.createWithAttributes("input", { type: "file", name: this.updateFieldScope(structure, key, scope), required: structure.required || false }),
742
589
  icon_wrapper = this.createWithAttributes("div", {"class": "image-icon-wrapper"}),
743
590
  explanation = this.createWithAttributes("p", {text: "Click here to upload an image"}),
744
591
  icon = this.createWithAttributes("core-icon", {icon: "image:photo"});
@@ -748,7 +595,7 @@
748
595
  wrapper.appendChild(icon_wrapper);
749
596
  wrapper.appendChild(explanation);
750
597
 
751
- this.addValidation('image', input)
598
+ this.addValidation('image', input, structure, key);
752
599
  input.addEventListener("click", function(e){ e.stopPropagation(); }, true)
753
600
 
754
601
  var self = this;
@@ -774,105 +621,131 @@
774
621
  return wrapper;
775
622
  }.bind(this),
776
623
 
624
+ file: function(){
625
+ var wrapper = this.createWithAttributes("span", { "class": "btn-file-upload" }),
626
+ decorator = this.createWithAttributes("gc-input-decorator", { "class": "file-decorator", label: this.capitalize(structure.label || key.replace(/_/g, " ")), floatingLabel: structure.options.floatingLabel }),
627
+ textInput = this.createWithAttributes("input", { type: "text", "class": "file-dummy" }),
628
+ input = this.createWithAttributes("input", { type: "file", name: this.updateFieldScope(structure, key, scope), required: structure.required || false });
629
+
630
+ decorator.appendChild(textInput);
631
+ wrapper.appendChild(input);
632
+ wrapper.appendChild(decorator);
633
+
634
+ this.addValidation('file', input, structure, key);
635
+ input.addEventListener("click", function(e){ e.stopPropagation(); }, true)
636
+
637
+ var self = this;
638
+ input.addEventListener("change", function(){
639
+ if (this.files.length){
640
+ textInput.value = Array.apply(null, this.files).map(function(item){ return item.name }).join(",");
641
+ self.addClass(wrapper.parentNode, "has-file");
642
+ self.data[self.key] = this.files;
643
+ decorator.updateLabelVisibility(this.files[0].name);
644
+
645
+ } else {
646
+ textInput.value = "";
647
+ self.removeClass(wrapper.parentNode, "has-file");
648
+ self.data[self.key] = null;
649
+ decorator.updateLabelVisibility("");
650
+ }
651
+ }, true)
652
+
653
+ return wrapper;
654
+ }.bind(this),
655
+
777
656
  html: function(){
778
657
  var content = document.createElement('div');
779
- content.innerHTML = this.structure[this.key].content;
658
+ content.innerHTML = structure.content;
780
659
  return content;
781
660
  }.bind(this)
782
661
 
783
662
  }
784
663
 
785
- try {
664
+ //try {
786
665
  return inputs[structure.type]()
787
- } catch(e){
788
- throw "Unknown field type: " + structure.type
789
- }
790
- },
791
-
792
- appendInputs: function(){
793
- while (this.inputList.length){
794
- var obj = this.inputList.shift(),
795
- container = this.shadowRoot.querySelector(obj.container);
796
-
797
- //TODO: insert points
798
- if (!obj.position || obj.position === 'append'){
799
- if (this.findParent(obj.element, obj.container) === null){
800
- container.appendChild(obj.element)
801
- }
802
- } else if (!!obj.position && obj.position === 'before') {
803
- if ([].indexOf.call(container.parentNode.childNodes, obj.element) === -1){
804
- container.parentNode.insertBefore(obj.element, container)
805
- }
806
- }
807
-
808
- if (!!obj.events.onFocus) container.addEventListener("focus", obj.events.onFocus, false);
809
- if (!!obj.events.onBlur) container.addEventListener("blur", obj.events.onBlur, false);
810
- }
666
+ //} catch(e){
667
+ // throw "Unknown field type: " + structure.type
668
+ //}
811
669
  },
812
670
 
813
- createBasicInput: function(type, name, label){
671
+ createBasicInput: function(type, data, structure, key, scope){
814
672
  var wrapper = document.createElement("gc-input-decorator"),
815
- input = document.createElement( type === "textarea" ? "textarea" : "input" );
673
+ input = document.createElement( type === "textarea" ? "textarea" : "input" );
816
674
 
817
- wrapper.label = this.capitalize(label || this.structure[this.key].label || this.key);
818
- wrapper.floatingLabel = this.structure[this.key].options.floatingLabel;
675
+ wrapper.label = this.capitalize(structure.label || key);
676
+ wrapper.floatingLabel = structure.options.floatingLabel;
819
677
 
820
678
  input.type = type;
821
- input.name = name || this.updateFieldScope();
822
- input.value = this.structure[this.key].value || '';
823
- input.id = this.scopeToClass(this.key)
824
- if (this.structure[this.key].required) input.required = true;
825
- input.bind('value', new PathObserver(this.data, this.key))
826
-
827
- this.addValidation(type, input)
679
+ input.name = this.updateFieldScope(structure, key, scope);
680
+ input.value = structure.value || '';
681
+ input.id = this.scopeToClass(structure, key, scope)
682
+ if (structure.required) input.required = true;
683
+ input.bind('value', new PathObserver(data, key))
684
+
685
+ this.addValidation(type, input, structure, key);
828
686
 
687
+ if (!!structure.autocomplete && typeof structure.autocomplete === "string"){
688
+ this.autocomplete(input, structure.autocomplete, structure.autocompleteOptions || {});
689
+ }
690
+
829
691
  if (type === 'textarea') input.rows = 3;
830
- if (type !== "hidden"){
692
+ if (type !== "hidden"){
831
693
  wrapper.appendChild(input);
832
- return wrapper;
833
- } else {
834
- return input;
835
- }
694
+ return wrapper;
695
+ } else {
696
+ return input;
697
+ }
836
698
 
837
699
  },
838
700
 
839
- createJSONField: function(fields){
840
- //TODO: only basic JSON support, if you want crazy complicated stuff, just use a textarea or custom input
701
+ createJSONField: function(fields, data, structure, key, scope){
702
+
703
+ // //TODO: only basic JSON support, if you want crazy complicated stuff, just use a textarea or custom input
841
704
  var wrapper = this.createWithAttributes('div', { "class": "json-wrapper" }),
842
- input = this.createWithAttributes("textarea", { name: this.updateFieldScope(), "class": "hidden", text: JSON.stringify(this.data[this.key]) }),
843
- isList = this.parentStructure !== null && this.parentStructure[this.parentContext].allowAdd;
844
-
845
- if (!this.data[this.key]) this.data[this.key] = {};
705
+ input = this.createWithAttributes("textarea", { name: this.updateFieldScope(structure, key, scope), "class": "hidden", text: JSON.stringify(data[key]) }),
706
+ isList = structure.allowAdd;
846
707
 
847
- new ObjectObserver(this.data[this.key]).open(function(){
848
- input.value = JSON.stringify(this.data[this.key])
849
- }.bind(this))
850
-
851
- // input.bind('value', new PathObserver(this.data, this.key))
852
-
853
- wrapper.appendChild(input);
854
- for (var i=0; i<fields.length; i++){
855
- this.data[this.key][fields[i]] = this.data[this.key][fields[i]] || "";
856
- var input_wrapper = this.createWithAttributes("div", { "class": (isList ? "input-wrapper" : "input") + " json-input-wrapper"})
857
- decorator = document.createElement("gc-input-decorator"),
858
- el = this.createWithAttributes('input');
859
-
860
- input_wrapper.appendChild(decorator);
861
- decorator.label = this.capitalize(fields[i]);
862
- decorator.floatingLabel = true;
863
- decorator.appendChild(el);
864
-
865
- el.bind("value", new PathObserver(this.data[this.key], fields[i]))
866
-
867
- if (isList){
868
- var className = "." + this.parentContext + "-" + this.getIndex(this.data) + " .input-wrapper." + this.key;
869
- var o = { element: input_wrapper, container: className, events: {}, position: "before" }
708
+ if (structure.multiple){
709
+ this.jsonData[key] = [{}]
710
+ this.buildStructure(structure.structure, this.jsonData[key][0], wrapper, null);
711
+ } else {
712
+ this.jsonData[key] = {}
713
+ this.buildStructure(structure.structure, this.jsonData[key], wrapper, null);
714
+ }
715
+ wrapper.appendChild(input)
716
+
717
+ var self = this;
718
+
719
+ function recurse(obj){
720
+ if (Array.isArray(obj)){
721
+ new ArrayObserver(obj).open(function(changed){
722
+ for (var j=0; j<changed.length; j++){
723
+ var changes = changed[j];
724
+ for (var i=0; i<changes.addedCount; i++){
725
+ recurse(obj[changes.index + i]);
726
+ }
727
+ }
728
+
729
+ input.value = JSON.stringify(self.jsonData[key]);
730
+ data[key] = JSON.parse(input.value);
731
+ })
732
+
733
+ for (var i=0; i<obj.length; i++) recurse(obj[i]);
870
734
  } else {
871
- var o = { element: input_wrapper, container: "." + this.scopeToClass(this.key), events: {} };
872
- }
735
+ new ObjectObserver(obj).open(function(){
736
+ input.value = JSON.stringify(self.jsonData[key]);
737
+ data[key] = JSON.parse(input.value);
738
+ });
873
739
 
874
- this.inputList.push(o)
740
+ for (var x in obj){
741
+ if (typeof obj[x] === 'object' && obj[x] !== null) recurse(obj[x]);
742
+ }
743
+ }
875
744
  }
745
+ recurse(this.jsonData[key]);
746
+
747
+ input.value = JSON.stringify(this.jsonData[key]);
748
+ data[key] = JSON.parse(input.value);
876
749
 
877
750
  return wrapper;
878
751
  },