1 (function (root, factory) { 2 3 if (typeof define === "function" && define.amd) { 4 define(factory); 5 } else if (typeof exports === "object") { 6 module.exports = factory(); 7 } else { 8 root.way = factory(); 9 } 10 11 }(this, function () { 12 13 "use strict"; 14 15 var way, w, tagPrefix = "way"; 16 17 ////////////////////////////// 18 // EVENT EMITTER DEFINITION // 19 ////////////////////////////// 20 21 var EventEmitter = function () { 22 23 this._watchers = {}; 24 this._watchersAll = {}; 25 26 }; 27 28 EventEmitter.prototype.constructor = EventEmitter; 29 30 EventEmitter.prototype.watchAll = function (handler) { 31 32 this._watchersAll = this._watchersAll || []; 33 if (!_w.contains(this._watchersAll, handler)) { this._watchersAll.push(handler); } 34 35 } 36 37 EventEmitter.prototype.watch = function (selector, handler) { 38 39 if (!this._watchers) { this._watchers = {}; } 40 this._watchers[selector] = this._watchers[selector] || []; 41 this._watchers[selector].push(handler); 42 43 } 44 45 EventEmitter.prototype.findWatcherDeps = function (selector) { 46 47 // Go up to look for parent watchers 48 // ex: if "some.nested.value" is the selector, it should also trigger for "some" 49 50 var result = []; 51 var watchers = _w.keys(this._watchers); 52 watchers.forEach(function (watcher) { 53 if (startsWith(selector, watcher)) { result.push(watcher); } 54 }); 55 return result; 56 57 } 58 59 EventEmitter.prototype.emitChange = function (selector /* , arguments */) { 60 61 if (!this._watchers) { this._watchers = {}; } 62 63 var self = this; 64 65 // Send data down to the local watchers 66 var deps = self.findWatcherDeps(selector); 67 deps.forEach(function (item) { 68 if (self._watchers[item]) { 69 self._watchers[item].forEach(function (handler) { 70 handler.apply(self, [self.get(item)]); 71 }); 72 } 73 }); 74 75 // Send data down to the global watchers 76 if (!self._watchersAll || !_w.isArray(self._watchersAll)) { return; } 77 self._watchersAll.forEach(function (watcher) { 78 if (_w.isFunction(watcher)) { watcher.apply(self, [selector, self.get(selector)]); } 79 }); 80 81 } 82 83 //////////////////// 84 // WAY DEFINITION // 85 //////////////////// 86 87 var WAY = function () { 88 89 this.data = {}; 90 this._bindings = {}; 91 this.options = { 92 persistent: true, 93 timeoutInput: 50, 94 timeoutDOM: 500 95 }; 96 97 }; 98 99 // Inherit from EventEmitter 100 WAY.prototype = Object.create(EventEmitter.prototype); 101 WAY.constructor = WAY; 102 103 ////////////////////////// 104 // DOM METHODS CHAINING // 105 ////////////////////////// 106 107 WAY.prototype.dom = function (element) { 108 109 this._element = w.dom(element).get(0); 110 return this; 111 112 }; 113 114 ////////////////////////////// 115 // DOM METHODS: DOM -> JSON // 116 ////////////////////////////// 117 118 WAY.prototype.toStorage = function (options, element) { 119 120 var self = this, 121 element = element || self._element, 122 options = options || self.dom(element).getOptions(), 123 data = self.dom(element).toJSON(options), 124 scope = self.dom(element).scope(), 125 selector = scope ? scope + "." + options.data : options.data; 126 127 if (options.readonly) { return false; } 128 self.set(selector, data, options); 129 130 } 131 132 WAY.prototype.toJSON = function (options, element) { 133 134 var self = this, 135 element = element || self._element, 136 data = self.dom(element).getValue(), 137 options = options || self.dom(element).getOptions(); 138 139 if (_w.isArray(options.pick)) { data = selectNested(data, options.pick, true); } 140 if (_w.isArray(options.omit)) { data = selectNested(data, options.omit, false); } 141 142 return data; 143 144 } 145 146 ////////////////////////////// 147 // DOM METHODS: JSON -> DOM // 148 ////////////////////////////// 149 150 WAY.prototype.fromStorage = function (options, element) { 151 152 var self = this, 153 element = element || self._element, 154 options = options || self.dom(element).getOptions(); 155 156 if (options.writeonly) { return false; } 157 158 var scope = self.dom(element).scope(), 159 selector = scope ? scope + "." + options.data : options.data, 160 data = self.get(selector); 161 162 self.dom(element).fromJSON(data, options); 163 164 } 165 166 WAY.prototype.fromJSON = function (data, options, element) { 167 168 var self = this, 169 element = element || self._element, 170 options = options || self.dom(element).getOptions(); 171 172 if (options.writeonly) { return false; } 173 174 if (_w.isObject(data)) { 175 if (_w.isArray(options.pick)) { data = selectNested(data, options.pick, true); } 176 if (_w.isArray(options.omit)) { data = selectNested(data, options.omit, false); } 177 var currentData = _w.isObject(self.dom(element).toJSON()) ? self.dom(element).toJSON() : {}; 178 data = _w.extend(currentData, data); 179 } 180 181 if (options.json) { data = _json.isStringified(data) ? data : _json.prettyprint(data); } 182 183 self.dom(element).setValue(data, options); 184 185 } 186 187 ///////////////////////////////// 188 // DOM METHODS: GET - SET HTML // 189 ///////////////////////////////// 190 191 WAY.prototype.getValue = function (element) { 192 193 var self = this, 194 element = element || self._element; 195 196 var getters = { 197 "SELECT": function () { 198 return w.dom(element).val(); 199 }, 200 "INPUT": function () { 201 var type = w.dom(element).type(); 202 if (_w.contains(["text", "password"], type)) { 203 return w.dom(element).val(); 204 } 205 if (_w.contains(["checkbox", "radio"], type)) { 206 return w.dom(element).prop("checked") ? w.dom(element).val() : null; 207 } 208 209 }, 210 "TEXTAREA": function () { 211 return w.dom(element).val(); 212 } 213 } 214 var defaultGetter = function (a) { 215 return w.dom(element).html(); 216 } 217 218 var elementType = w.dom(element).get(0).tagName; 219 var getter = getters[elementType] || defaultGetter; 220 return getter(); 221 222 } 223 224 WAY.prototype._transforms = { 225 uppercase: function (data) { 226 return _w.isString(data) ? data.toUpperCase() : data; 227 }, 228 lowercase: function (data) { 229 return _w.isString(data) ? data.toLowerCase() : data; 230 }, 231 reverse: function (data) { 232 return data && data.split && _w.isFunction(data.split) ? data.split("").reverse().join("") : data; 233 } 234 }; 235 236 WAY.prototype.registerTransform = function (name, transform) { 237 var self = this; 238 if (_w.isFunction(transform)) { self._transforms[name] = transform; } 239 } 240 241 WAY.prototype.setValue = function (data, options, element) { 242 243 var self = this, 244 element = element || self._element, 245 options = options || self.dom(element).getOptions(); 246 247 options.transform = options.transform || []; 248 options.transform.forEach(function (transformName) { 249 var transform = self._transforms[transformName] || function (data) { return data }; 250 data = transform(data); 251 }); 252 253 var setters = { 254 255 "SELECT": function (a) { 256 w.dom(element).val(a); 257 }, 258 "INPUT": function (a) { 259 if (!_w.isString(a)) { a = JSON.stringify(a); } 260 var type = w.dom(element).get(0).type; 261 if (_w.contains(["text", "password"], type)) { 262 w.dom(element).val(a || ""); 263 } 264 if (_w.contains(["checkbox", "radio"], type)) { 265 if (a === w.dom(element).val()) { 266 w.dom(element).prop("checked", true); 267 } else { 268 w.dom(element).prop("checked", false); 269 } 270 } 271 }, 272 "TEXTAREA": function (a) { 273 if (!_w.isString(a)) { a = JSON.stringify(a); } 274 w.dom(element).val(a || ""); 275 }, 276 "PRE": function (a) { 277 if (options.html) { 278 w.dom(element).html(a); 279 } else { 280 w.dom(element).text(a); 281 } 282 }, 283 "IMG": function (a) { 284 285 if (!a) { 286 a = options.default || ""; 287 w.dom(element).attr("src", a); 288 return false; 289 } 290 291 var isValidImageUrl = function (url, cb) { 292 w.dom(element).addClass("way-loading"); 293 w.dom("img", { 294 src: url, 295 onerror: function () { cb(false); }, 296 onload: function () { cb(true); } 297 }); 298 } 299 300 isValidImageUrl(a, function (response) { 301 w.dom(element).removeClass("way-loading"); 302 if (response) { 303 w.dom(element).removeClass("way-error").addClass("way-success"); 304 } else { 305 if (a) { 306 w.dom(element).addClass("way-error"); 307 } else { 308 w.dom(element).removeClass("way-error").removeClass("way-success"); 309 } 310 a = options.default || ""; 311 } 312 w.dom(element).attr("src", a); 313 }); 314 315 } 316 317 } 318 var defaultSetter = function (a) { 319 320 if (options.html) { 321 w.dom(element).html(a); 322 } else { 323 w.dom(element).text(a); 324 } 325 326 } 327 328 var elementType = w.dom(element).get(0).tagName; 329 var setter = setters[elementType] || defaultSetter; 330 setter(data); 331 332 } 333 334 WAY.prototype.setDefault = function (force, options, element) { 335 336 var self = this, 337 element = element || self._element, 338 force = force || false, 339 options = options ? _w.extend(self.dom(element).getOptions(), options) : self.dom(element).getOptions(); 340 341 // Should we just set the default value in the DOM, or also in the datastore? 342 if (!options.default) { return false; } 343 if (force) { 344 self.set(options.data, options.default, options); 345 } else { 346 self.dom(element).setValue(options.default, options); 347 } 348 349 } 350 351 WAY.prototype.setDefaults = function () { 352 353 var self = this, 354 dataSelector = "[" + tagPrefix + "-default]"; 355 356 var elements = w.dom(dataSelector).get(); 357 for (var i in elements) { 358 var element = elements[i], 359 options = self.dom(element).getOptions(), 360 selector = options.data || null, 361 data = selector ? self.get(selector) : null; 362 if (!data) { self.dom(element).setDefault(); } 363 } 364 365 } 366 367 ///////////////////////////////////// 368 // DOM METHODS: GET - SET BINDINGS // 369 ///////////////////////////////////// 370 371 // Scans the DOM to look for new bindings 372 WAY.prototype.registerBindings = function () { 373 374 // Dealing with bindings removed from the DOM by just resetting all the bindings all the time. 375 // Isn't there a better way? 376 // One idea would be to add a "way-bound" class to bound elements 377 // self._bindings = {}; 378 379 var self = this; 380 var selector = "[" + tagPrefix + "-data]"; 381 self._bindings = {}; 382 383 var elements = w.dom(selector).get(); 384 for (var i in elements) { 385 var element = elements[i], 386 options = self.dom(element).getOptions(), 387 scope = self.dom(element).scope(), 388 selector = scope ? scope + "." + options.data : options.data; 389 390 self._bindings[selector] = self._bindings[selector] || []; 391 if (!_w.contains(self._bindings[selector], w.dom(element).get(0))) { 392 self._bindings[selector].push(w.dom(element).get(0)); 393 } 394 395 } 396 397 } 398 399 WAY.prototype.updateBindings = function (selector) { 400 401 var self = this; 402 self._bindings = self._bindings || {}; 403 404 // Set bindings for the data selector 405 var bindings = pickAndMergeParentArrays(self._bindings, selector); 406 bindings.forEach(function (element) { 407 var focused = (w.dom(element).get(0) === w.dom(":focus").get(0)) ? true : false; 408 if (!focused) { self.dom(element).fromStorage(); } 409 }); 410 411 // Set bindings for the global selector 412 if (self._bindings["__all__"]) { 413 self._bindings["__all__"].forEach(function (element) { 414 self.dom(element).fromJSON(self.data); 415 }); 416 } 417 418 } 419 420 //////////////////////////////////// 421 // DOM METHODS: GET - SET REPEATS // 422 //////////////////////////////////// 423 424 WAY.prototype.registerRepeats = function () { 425 426 // Register repeats 427 var self = this; 428 var selector = "[" + tagPrefix + "-repeat]"; 429 self._repeats = self._repeats || {}; 430 self._repeatsCount = self._repeatsCount || 0; 431 432 var elements = w.dom(selector).get(); 433 for (var i in elements) { 434 var element = elements[i], 435 options = self.dom(element).getOptions(); 436 437 self._repeats[options.repeat] = self._repeats[options.repeat] || []; 438 439 var wrapperAttr = tagPrefix + "-repeat-wrapper="" + self._repeatsCount + """, 440 parent = w.dom(element).parent("[" + wrapperAttr + "]"); 441 if (!parent.length) { 442 443 self._repeats[options.repeat].push({ 444 id: self._repeatsCount, 445 element: w.dom(element).clone(true).removeAttr(tagPrefix + "-repeat").removeAttr(tagPrefix + "-filter").get(0), 446 selector: options.repeat, 447 filter: options.filter 448 }); 449 450 var wrapper = document.createElement("div"); 451 w.dom(wrapper).attr(tagPrefix + "-repeat-wrapper", self._repeatsCount); 452 w.dom(wrapper).attr(tagPrefix + "-scope", options.repeat); 453 if (options.filter) { w.dom(wrapper).attr(tagPrefix + "-filter", options.filter); } 454 455 w.dom(element).replaceWith(wrapper); 456 self.updateRepeats(options.repeat); 457 458 self._repeatsCount++; 459 460 } 461 462 } 463 464 } 465 466 /* 467 WAY.prototype._filters = { 468 noFalsy: function(item ) { 469 if (!item) { 470 return false; 471 } else { 472 return true; 473 } 474 } 475 }; 476 477 WAY.prototype.registerFilter = function(name, filter) { 478 var self = this; 479 if (_w.isFunction(filter)) { self._filters[name] = filter; } 480 } 481 */ 482 483 WAY.prototype.updateRepeats = function (selector) { 484 485 var self = this; 486 self._repeats = self._repeats || {}; 487 488 var repeats = pickAndMergeParentArrays(self._repeats, selector); 489 490 repeats.forEach(function (repeat) { 491 492 var wrapper = "[" + tagPrefix + "-repeat-wrapper="" + repeat.id + ""]", 493 data = self.get(repeat.selector), 494 items = []; 495 496 repeat.filter = repeat.filter || []; 497 w.dom(wrapper).empty(); 498 499 for (var key in data) { 500 501 /* 502 var item = data[key], 503 test = true; 504 for (var i in repeat.filter) { 505 var filterName = repeat.filter[i]; 506 var filter = self._filters[filterName] || function(data) { return data }; 507 test = filter(item); 508 if (!test) { break; } 509 } 510 if (!test) { continue; } 511 */ 512 513 w.dom(repeat.element).attr(tagPrefix + "-scope", key); 514 var html = w.dom(repeat.element).get(0).outerHTML; 515 html = html.replace(/$$key/gi, key); 516 items.push(html); 517 518 } 519 520 w.dom(wrapper).html(items.join("")); 521 self.registerBindings(); 522 self.updateBindings(); 523 524 }); 525 526 } 527 528 //////////////////////// 529 // DOM METHODS: FORMS // 530 //////////////////////// 531 532 WAY.prototype.updateForms = function () { 533 534 // If we just parse the forms with form2js (see commits before 08/19/2014) and set the data with way.set(), 535 // we reset the entire data for this pathkey in the datastore. It causes the bug 536 // reported here: https://github.com/gwendall/way.js/issues/10 537 // Solution: 538 // 1. watch new forms with a [way-data] attribute 539 // 2. remove this attribute 540 // 3. attach the appropriate attributes to its child inputs 541 // -> so that each input is set separately to way.js' datastore 542 543 var self = this; 544 var selector = "form[" + tagPrefix + "-data]"; 545 546 var elements = w.dom(selector).get(); 547 for (var i in elements) { 548 549 var form = elements[i], 550 options = self.dom(form).getOptions(), 551 formDataSelector = options.data; 552 w.dom(form).removeAttr(tagPrefix + "-data"); 553 554 // Reverse needed to set the right index for "[]" names 555 var inputs = w.dom(form).find("[name]").reverse().get(); 556 for (var i in inputs) { 557 558 var input = inputs[i], 559 name = w.dom(input).attr("name"); 560 561 if (endsWith(name, "[]")) { 562 var array = name.split("[]")[0], 563 arraySelector = "[name^='" + array + "']", 564 arrayIndex = w.dom(form).find(arraySelector).get().length; 565 name = array + "." + arrayIndex; 566 } 567 var selector = formDataSelector + "." + name; 568 options.data = selector; 569 self.dom(input).setOptions(options); 570 w.dom(input).removeAttr("name"); 571 572 } 573 574 } 575 576 } 577 578 ///////////////////////////////////////////// 579 // DOM METHODS: GET - SET ALL DEPENDENCIES // 580 ///////////////////////////////////////////// 581 582 WAY.prototype.registerDependencies = function () { 583 584 this.registerBindings(); 585 this.registerRepeats(); 586 587 } 588 589 WAY.prototype.updateDependencies = function (selector) { 590 591 this.updateBindings(selector); 592 this.updateRepeats(selector); 593 this.updateForms(selector); 594 595 } 596 597 ////////////////////////////////// 598 // DOM METHODS: OPTIONS PARSING // 599 ////////////////////////////////// 600 601 WAY.prototype.setOptions = function (options, element) { 602 603 var self = this, 604 element = self._element || element; 605 606 for (var k in options) { 607 var attr = tagPrefix + "-" + k, 608 value = options[k]; 609 w.dom(element).attr(attr, value); 610 } 611 612 } 613 614 WAY.prototype.getOptions = function (element) { 615 616 var self = this, 617 element = element || self._element, 618 defaultOptions = { 619 data: null, 620 html: false, 621 readonly: false, 622 writeonly: false, 623 persistent: false 624 }; 625 return _w.extend(defaultOptions, self.dom(element).getAttrs(tagPrefix)); 626 627 } 628 629 WAY.prototype.getAttrs = function (prefix, element) { 630 631 var self = this, 632 element = element || self._element; 633 634 var parseAttrValue = function (key, value) { 635 636 var attrTypes = { 637 pick: "array", 638 omit: "array", 639 readonly: "boolean", 640 writeonly: "boolean", 641 json: "boolean", 642 html: "boolean", 643 persistent: "boolean" 644 }; 645 646 var parsers = { 647 array: function (value) { 648 return value.split(","); 649 }, 650 boolean: function (value) { 651 if (value === "true") { return true; } 652 if (value === "false") { return false; } 653 return true; 654 } 655 }; 656 var defaultParser = function () { return value; }; 657 var valueType = attrTypes[key] || null; 658 var parser = parsers[valueType] || defaultParser; 659 660 return parser(value); 661 662 } 663 664 var attributes = {}; 665 var attrs = [].slice.call(w.dom(element).get(0).attributes); 666 attrs.forEach(function (attr) { 667 var include = (prefix && startsWith(attr.name, prefix + "-")) ? true : false; 668 if (include) { 669 var name = (prefix) ? attr.name.slice(prefix.length + 1, attr.name.length) : attr.name; 670 var value = parseAttrValue(name, attr.value); 671 if (_w.contains(["transform", "filter"], name)) { value = value.split("|"); } 672 attributes[name] = value; 673 } 674 }); 675 676 return attributes; 677 678 } 679 680 ////////////////////////// 681 // DOM METHODS: SCOPING // 682 ////////////////////////// 683 684 WAY.prototype.scope = function (options, element) { 685 686 var self = this, 687 element = element || self._element, 688 scopeAttr = tagPrefix + "-scope", 689 scopeBreakAttr = tagPrefix + "-scope-break", 690 scopes = [], 691 scope = ""; 692 693 var parentsSelector = "[" + scopeBreakAttr + "], [" + scopeAttr + "]"; 694 var elements = w.dom(element).parents(parentsSelector).get(); 695 for (var i in elements) { 696 var el = elements[i]; 697 if (w.dom(el).attr(scopeBreakAttr)) { break; } 698 var attr = w.dom(el).attr(scopeAttr); 699 scopes.unshift(attr); 700 } 701 if (w.dom(element).attr(scopeAttr)) { scopes.push(w.dom(element).attr(scopeAttr)); } 702 if (w.dom(element).attr(scopeBreakAttr)) { scopes = []; } 703 704 scope = _w.compact(scopes).join("."); 705 706 return scope; 707 708 } 709 710 ////////////////// 711 // DATA METHODS // 712 ////////////////// 713 714 WAY.prototype.get = function (selector) { 715 716 var self = this; 717 if (selector !== undefined && !_w.isString(selector)) { return false; } 718 if (!self.data) { return {}; } 719 return selector ? _json.get(self.data, selector) : self.data; 720 721 } 722 723 WAY.prototype.set = function (selector, value, options) { 724 725 if (!selector) { return false; } 726 if (selector.split(".")[0] === "this") { 727 console.log("Sorry, "this" is a reserved word in way.js"); 728 return false; 729 } 730 731 var self = this; 732 options = options || {}; 733 734 if (selector) { 735 736 if (!_w.isString(selector)) { return false; } 737 self.data = self.data || {}; 738 self.data = selector ? _json.set(self.data, selector, value) : {}; 739 740 self.updateDependencies(selector); 741 self.emitChange(selector, value); 742 if (options.persistent) { self.backup(selector); } 743 } 744 745 } 746 747 WAY.prototype.push = function (selector, value, options) { 748 749 if (!selector) { return false; } 750 751 var self = this; 752 options = options || {}; 753 754 if (selector) { 755 self.data = selector ? _json.push(self.data, selector, value, true) : {}; 756 } 757 758 self.updateDependencies(selector); 759 self.emitChange(selector, null); 760 if (options.persistent) { self.backup(selector); } 761 762 } 763 764 WAY.prototype.remove = function (selector, options) { 765 766 var self = this; 767 options = options || {}; 768 769 if (selector) { 770 self.data = _json.remove(self.data, selector); 771 } else { 772 self.data = {}; 773 } 774 775 self.updateDependencies(selector); 776 self.emitChange(selector, null); 777 if (options.persistent) { self.backup(selector); } 778 779 } 780 781 WAY.prototype.clear = function () { 782 783 this.remove(null, { persistent: true }); 784 785 } 786 787 ////////////////////////// 788 // LOCALSTORAGE METHODS // 789 ////////////////////////// 790 791 WAY.prototype.backup = function () { 792 793 var self = this; 794 if (!self.options.persistent) { return; } 795 try { 796 var data = self.data || {}; 797 localStorage.setItem(tagPrefix, JSON.stringify(data)); 798 } catch (e) { 799 console.log("Your browser does not support localStorage."); 800 } 801 802 } 803 804 WAY.prototype.restore = function () { 805 806 var self = this; 807 if (!self.options.persistent) { return; } 808 try { 809 var data = localStorage.getItem(tagPrefix); 810 try { 811 data = JSON.parse(data); 812 for (var key in data) { 813 self.set(key, data[key]); 814 } 815 } catch (e) { } 816 } catch (e) { 817 console.log("Your browser does not support localStorage."); 818 } 819 820 } 821 822 ////////// 823 // MISC // 824 ////////// 825 826 var matchesSelector = function (el, selector) { 827 var matchers = ["matches", "matchesSelector", "webkitMatchesSelector", "mozMatchesSelector", "msMatchesSelector", "oMatchesSelector"], 828 fn = null; 829 for (var i in matchers) { 830 fn = matchers[i]; 831 if (_w.isFunction(el[fn])) { 832 return el[fn](selector); 833 } 834 } 835 return false; 836 } 837 838 var startsWith = function (str, starts) { 839 840 if (starts === "") { return true; } 841 if (str === null || starts === null) { return false; } 842 str = String(str); starts = String(starts); 843 return str.length >= starts.length && str.slice(0, starts.length) === starts; 844 845 } 846 847 var endsWith = function (str, ends) { 848 849 if (ends === "") { return true; } 850 if (str === null || ends === null) { return false; } 851 str = String(str); ends = String(ends); 852 return str.length >= ends.length && str.slice(str.length - ends.length, str.length) === ends; 853 854 } 855 856 var cleanEmptyKeys = function (object) { 857 858 return _w.pick(object, _w.compact(_w.keys(object))); 859 860 } 861 862 var filterStartingWith = function (object, string, type) { // true: pick - false: omit 863 864 var keys = _w.keys(object); 865 keys.forEach(function (key) { 866 if (type) { 867 if (!startsWith(key, string)) { delete object[key]; } 868 } else { 869 if (startsWith(key, string)) { delete object[key]; } 870 } 871 }); 872 return object; 873 874 } 875 876 var selectNested = function (data, keys, type) { // true: pick - false: omit 877 878 // Flatten / unflatten to allow for nested picks / omits (doesn't work with regular pick) 879 // ex: data = {something:{nested:"value"}} 880 // keys = ['something.nested'] 881 882 var flat = _json.flatten(data); 883 for (var i in keys) flat = filterStartingWith(flat, keys[i], type); 884 var unflat = _json.unflatten(flat); 885 // Unflatten returns an object with an empty property if it is given an empty object 886 return cleanEmptyKeys(unflat); 887 888 } 889 890 var pickAndMergeParentArrays = function (object, selector) { 891 892 // Example: 893 // object = { a: [1,2,3], a.b: [4,5,6], c: [7,8,9] } 894 // fn(object, "a.b") 895 // > [1,2,3,4,5,6] 896 897 var keys = []; 898 if (selector) { 899 900 // Set bindings for the specified selector 901 902 // (bindings that are repeat items) 903 var split = selector.split("."), 904 lastKey = split[split.length - 1], 905 isArrayItem = !isNaN(lastKey); 906 907 if (isArrayItem) { 908 split.pop(); 909 var key = split.join("."); 910 keys = object[key] ? _w.union(keys, object[key]) : keys; 911 } 912 913 // (bindings with keys starting with, to include nested bindings) 914 for (var key in object) { 915 if (startsWith(key, selector)) { keys = _w.union(keys, object[key]); } 916 } 917 918 } else { 919 920 // Set bindings for all selectors 921 for (var key in object) { 922 keys = _w.union(keys, object[key]); 923 } 924 925 } 926 return keys; 927 928 } 929 930 var isPrintableKey = function (e) { 931 932 var keycode = e.keyCode; 933 if (!keycode) { return true; } 934 935 var valid = 936 (keycode === 8) || // delete 937 (keycode > 47 && keycode < 58) || // number keys 938 keycode === 32 || keycode === 13 || // spacebar & return key(s) (if you want to allow carriage returns) 939 (keycode > 64 && keycode < 91) || // letter keys 940 (keycode > 95 && keycode < 112) || // numpad keys 941 (keycode > 185 && keycode < 193) || // ;=,-./` (in order) 942 (keycode > 218 && keycode < 223); // []' (in order) 943 944 return valid; 945 946 } 947 948 var escapeHTML = function (str) { 949 return str && _w.isString(str) ? str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") : str; 950 } 951 952 /////////////////////////////////////////////////// 953 // _w (strip of the required underscore methods) // 954 /////////////////////////////////////////////////// 955 956 var _w = {}; 957 958 var ArrayProto = Array.prototype, 959 ObjProto = Object.prototype, 960 FuncProto = Function.prototype; 961 962 var nativeIsArray = Array.isArray, 963 nativeKeys = Object.keys, 964 nativeBind = FuncProto.bind; 965 966 var 967 push = ArrayProto.push, 968 slice = ArrayProto.slice, 969 concat = ArrayProto.concat, 970 toString = ObjProto.toString, 971 hasOwnProperty = ObjProto.hasOwnProperty; 972 973 var flatten = function (input, shallow, strict, output) { 974 if (shallow && _w.every(input, _w.isArray)) { 975 return concat.apply(output, input); 976 } 977 for (var i = 0, length = input.length; i < length; i++) { 978 var value = input[i]; 979 if (!_w.isArray(value) && !_w.isArguments(value)) { 980 if (!strict) output.push(value); 981 } else if (shallow) { 982 push.apply(output, value); 983 } else { 984 flatten(value, shallow, strict, output); 985 } 986 } 987 return output; 988 }; 989 990 var createCallback = function (func, context, argCount) { 991 if (context === void 0) return func; 992 switch (argCount == null ? 3 : argCount) { 993 case 1: return function (value) { 994 return func.call(context, value); 995 }; 996 case 2: return function (value, other) { 997 return func.call(context, value, other); 998 }; 999 case 3: return function (value, index, collection) { 1000 return func.call(context, value, index, collection); 1001 }; 1002 case 4: return function (accumulator, value, index, collection) { 1003 return func.call(context, accumulator, value, index, collection); 1004 }; 1005 } 1006 return function () { 1007 return func.apply(context, arguments); 1008 }; 1009 }; 1010 1011 _w.compact = function (array) { 1012 return _w.filter(array, _w.identity); 1013 }; 1014 1015 _w.filter = function (obj, predicate, context) { 1016 var results = []; 1017 if (obj == null) return results; 1018 predicate = _w.iteratee(predicate, context); 1019 _w.each(obj, function (value, index, list) { 1020 if (predicate(value, index, list)) results.push(value); 1021 }); 1022 return results; 1023 }; 1024 1025 _w.identity = function (value) { 1026 return value; 1027 }; 1028 1029 _w.every = function (obj, predicate, context) { 1030 if (obj == null) return true; 1031 predicate = _w.iteratee(predicate, context); 1032 var keys = obj.length !== +obj.length && _w.keys(obj), 1033 length = (keys || obj).length, 1034 index, currentKey; 1035 for (index = 0; index < length; index++) { 1036 currentKey = keys ? keys[index] : index; 1037 if (!predicate(obj[currentKey], currentKey, obj)) return false; 1038 } 1039 return true; 1040 }; 1041 1042 _w.union = function () { 1043 return _w.uniq(flatten(arguments, true, true, [])); 1044 }; 1045 1046 _w.uniq = function (array, isSorted, iteratee, context) { 1047 if (array == null) return []; 1048 if (!_w.isBoolean(isSorted)) { 1049 context = iteratee; 1050 iteratee = isSorted; 1051 isSorted = false; 1052 } 1053 if (iteratee != null) iteratee = _w.iteratee(iteratee, context); 1054 var result = []; 1055 var seen = []; 1056 for (var i = 0, length = array.length; i < length; i++) { 1057 var value = array[i]; 1058 if (isSorted) { 1059 if (!i || seen !== value) result.push(value); 1060 seen = value; 1061 } else if (iteratee) { 1062 var computed = iteratee(value, i, array); 1063 if (_w.indexOf(seen, computed) < 0) { 1064 seen.push(computed); 1065 result.push(value); 1066 } 1067 } else if (_w.indexOf(result, value) < 0) { 1068 result.push(value); 1069 } 1070 } 1071 return result; 1072 }; 1073 1074 _w.pick = function (obj, iteratee, context) { 1075 var result = {}, key; 1076 if (obj == null) return result; 1077 if (_w.isFunction(iteratee)) { 1078 iteratee = createCallback(iteratee, context); 1079 for (key in obj) { 1080 var value = obj[key]; 1081 if (iteratee(value, key, obj)) result[key] = value; 1082 } 1083 } else { 1084 var keys = concat.apply([], slice.call(arguments, 1)); 1085 obj = new Object(obj); 1086 for (var i = 0, length = keys.length; i < length; i++) { 1087 key = keys[i]; 1088 if (key in obj) result[key] = obj[key]; 1089 } 1090 } 1091 return result; 1092 }; 1093 1094 _w.has = function (obj, key) { 1095 return obj != null && hasOwnProperty.call(obj, key); 1096 }; 1097 1098 _w.keys = function (obj) { 1099 if (!_w.isObject(obj)) return []; 1100 if (nativeKeys) return nativeKeys(obj); 1101 var keys = []; 1102 for (var key in obj) if (_w.has(obj, key)) keys.push(key); 1103 return keys; 1104 }; 1105 1106 _w.contains = function (obj, target) { 1107 if (obj == null) return false; 1108 if (obj.length !== +obj.length) obj = _w.values(obj); 1109 return _w.indexOf(obj, target) >= 0; 1110 }; 1111 1112 _w.sortedIndex = function (array, obj, iteratee, context) { 1113 iteratee = _w.iteratee(iteratee, context, 1); 1114 var value = iteratee(obj); 1115 var low = 0, high = array.length; 1116 while (low < high) { 1117 var mid = low + high >>> 1; 1118 if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; 1119 } 1120 return low; 1121 }; 1122 1123 _w.property = function (key) { 1124 return function (obj) { 1125 return obj[key]; 1126 }; 1127 }; 1128 1129 _w.iteratee = function (value, context, argCount) { 1130 if (value == null) return _w.identity; 1131 if (_w.isFunction(value)) return createCallback(value, context, argCount); 1132 if (_w.isObject(value)) return _w.matches(value); 1133 return _w.property(value); 1134 }; 1135 1136 _w.pairs = function (obj) { 1137 var keys = _w.keys(obj); 1138 var length = keys.length; 1139 var pairs = Array(length); 1140 for (var i = 0; i < length; i++) { 1141 pairs[i] = [keys[i], obj[keys[i]]]; 1142 } 1143 return pairs; 1144 }; 1145 1146 _w.matches = function (attrs) { 1147 var pairs = _w.pairs(attrs), length = pairs.length; 1148 return function (obj) { 1149 if (obj == null) return !length; 1150 obj = new Object(obj); 1151 for (var i = 0; i < length; i++) { 1152 var pair = pairs[i], key = pair[0]; 1153 if (pair[1] !== obj[key] || !(key in obj)) return false; 1154 } 1155 return true; 1156 }; 1157 }; 1158 1159 _w.indexOf = function (array, item, isSorted) { 1160 if (array == null) return -1; 1161 var i = 0, length = array.length; 1162 if (isSorted) { 1163 if (typeof isSorted == 'number') { 1164 i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; 1165 } else { 1166 i = _w.sortedIndex(array, item); 1167 return array[i] === item ? i : -1; 1168 } 1169 } 1170 for (; i < length; i++) if (array[i] === item) return i; 1171 return -1; 1172 }; 1173 1174 _w.values = function (obj) { 1175 var keys = _w.keys(obj); 1176 var length = keys.length; 1177 var values = Array(length); 1178 for (var i = 0; i < length; i++) { 1179 values[i] = obj[keys[i]]; 1180 } 1181 return values; 1182 }; 1183 1184 _w.extend = function (obj) { 1185 if (!_w.isObject(obj)) return obj; 1186 var source, prop; 1187 for (var i = 1, length = arguments.length; i < length; i++) { 1188 source = arguments[i]; 1189 for (prop in source) { 1190 if (hasOwnProperty.call(source, prop)) { 1191 obj[prop] = source[prop]; 1192 } 1193 } 1194 } 1195 return obj; 1196 }; 1197 1198 _w.isArray = function (obj) { 1199 return toString.call(obj) === '[object Array]'; 1200 }; 1201 1202 _w.isBoolean = function (obj) { 1203 return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; 1204 }; 1205 1206 _w.isUndefined = function (obj) { 1207 return obj === void 0; 1208 }; 1209 1210 _w.isObject = function (obj) { 1211 var type = typeof obj; 1212 return type === 'function' || type === 'object' && !!obj; 1213 }; 1214 1215 _w.each = function (obj, iteratee, context) { 1216 if (obj == null) return obj; 1217 iteratee = createCallback(iteratee, context); 1218 var i, length = obj.length; 1219 if (length === +length) { 1220 for (i = 0; i < length; i++) { 1221 iteratee(obj[i], i, obj); 1222 } 1223 } else { 1224 var keys = _w.keys(obj); 1225 for (i = 0, length = keys.length; i < length; i++) { 1226 iteratee(obj[keys[i]], keys[i], obj); 1227 } 1228 } 1229 return obj; 1230 }; 1231 1232 _w.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function (name) { 1233 _w['is' + name] = function (obj) { 1234 return toString.call(obj) === '[object ' + name + ']'; 1235 }; 1236 }); 1237 1238 /////////////////////////////////////////////////////////// 1239 // _json (strip of the required underscore.json methods) // 1240 /////////////////////////////////////////////////////////// 1241 1242 var deepJSON = function (obj, key, value, remove) { 1243 1244 var keys = key.replace(/[(["']?)([^1]+?)1?]/g, '.$2').replace(/^./, '').split('.'), 1245 root, 1246 i = 0, 1247 n = keys.length; 1248 1249 // Set deep value 1250 if (arguments.length > 2) { 1251 1252 root = obj; 1253 n--; 1254 1255 while (i < n) { 1256 key = keys[i++]; 1257 obj = obj[key] = _w.isObject(obj[key]) ? obj[key] : {}; 1258 } 1259 1260 if (remove) { 1261 if (_w.isArray(obj)) { 1262 obj.splice(keys[i], 1); 1263 } else { 1264 delete obj[keys[i]]; 1265 } 1266 } else { 1267 obj[keys[i]] = value || ""; 1268 } 1269 1270 value = root; 1271 1272 // Get deep value 1273 } else { 1274 while ((obj = obj[keys[i++]]) != null && i < n) { }; 1275 value = i < n ? void 0 : obj; 1276 } 1277 if (value == null) { 1278 value = ""; 1279 } 1280 return value; 1281 1282 } 1283 1284 var _json = {} 1285 1286 _json.VERSION = '0.1.0'; 1287 _json.debug = true; 1288 1289 _json.exit = function (source, reason, data, value) { 1290 1291 if (!_json.debug) return; 1292 1293 var messages = {}; 1294 messages.noJSON = "Not a JSON"; 1295 messages.noString = "Not a String"; 1296 messages.noArray = "Not an Array"; 1297 messages.missing = "Missing argument"; 1298 1299 var error = { source: source, data: data, value: value }; 1300 error.message = messages[reason] ? messages[reason] : "No particular reason"; 1301 console.log("Error", error); 1302 return; 1303 1304 } 1305 1306 _json.is = function (json) { 1307 1308 return (toString.call(json) == "[object Object]"); 1309 1310 } 1311 1312 _json.isStringified = function (string) { 1313 1314 var test = false; 1315 try { 1316 test = /^[],:{}s]*$/.test(string.replace(/\["\/bfnrtu]/g, '@'). 1317 replace(/"[^"\ ]*"|true|false|null|-?d+(?:.d*)?(?:[eE][+-]?d+)?/g, ']'). 1318 replace(/(?:^|:|,)(?:s*[)+/g, '')); 1319 } catch (e) { } 1320 return test; 1321 1322 } 1323 1324 _json.get = function (json, selector) { 1325 1326 if (json == undefined) return _json.exit("get", "missing", "json", json); 1327 if (selector == undefined) return _json.exit("get", "missing", "selector", selector); 1328 if (!_w.isString(selector)) return _json.exit("get", "noString", "selector", selector); 1329 return deepJSON(json, selector); 1330 1331 }; 1332 1333 _json.set = function (json, selector, value) { 1334 1335 if (json == undefined) return _json.exit("set", "missing", "json", json); 1336 if (selector == undefined) return _json.exit("set", "missing", "selector", selector); 1337 if (!_w.isString(selector)) return _json.exit("set", "noString", "selector", selector); 1338 return value ? deepJSON(json, selector, value) : _json.remove(json, selector); 1339 // return deepJSON(json, selector, value); // Now removes the property if the value is empty. Maybe should keep it instead? 1340 1341 }; 1342 1343 _json.remove = function (json, selector) { 1344 1345 if (json == undefined) return _json.exit("remove", "missing", "json", json); 1346 if (selector == undefined) return _json.exit("remove", "missing", "selector", selector); 1347 if (!_w.isString(selector)) return _json.exit("remove", "noString", "selector", selector); 1348 return deepJSON(json, selector, null, true); 1349 1350 } 1351 1352 _json.push = function (json, selector, value, force) { 1353 1354 if (json == undefined) return _json.exit("push", "missing", "json", json); 1355 if (selector == undefined) return _json.exit("push", "missing", "selector", selector); 1356 var array = _json.get(json, selector); 1357 if (!_w.isArray(array)) { 1358 if (force) { 1359 array = []; 1360 } else { 1361 return _json.exit("push", "noArray", "array", array); 1362 } 1363 } 1364 array.push(value); 1365 return _json.set(json, selector, array); 1366 1367 } 1368 1369 _json.unshift = function (json, selector, value) { 1370 1371 if (json == undefined) return _json.exit("unshift", "missing", "json", json); 1372 if (selector == undefined) return _json.exit("unshift", "missing", "selector", selector); 1373 if (value == undefined) return _json.exit("unshift", "missing", "value", value); 1374 var array = _json.get(json, selector); 1375 if (!_w.isArray(array)) return _json.exit("unshift", "noArray", "array", array); 1376 array.unshift(value); 1377 return _json.set(json, selector, array); 1378 1379 } 1380 1381 _json.flatten = function (json) { 1382 1383 if (json.constructor.name != "Object") return _json.exit("flatten", "noJSON", "json", json); 1384 1385 var result = {}; 1386 function recurse(cur, prop) { 1387 if (Object(cur) !== cur) { 1388 result[prop] = cur; 1389 } else if (Array.isArray(cur)) { 1390 for (var i = 0, l = cur.length; i < l; i++) { 1391 recurse(cur[i], prop ? prop + "." + i : "" + i); 1392 if (l == 0) result[prop] = []; 1393 } 1394 } else { 1395 var isEmpty = true; 1396 for (var p in cur) { 1397 isEmpty = false; 1398 recurse(cur[p], prop ? prop + "." + p : p); 1399 } 1400 if (isEmpty) result[prop] = {}; 1401 } 1402 } 1403 recurse(json, ""); 1404 return result; 1405 1406 } 1407 1408 _json.unflatten = function (data) { 1409 1410 if (Object(data) !== data || Array.isArray(data)) 1411 return data; 1412 var result = {}, cur, prop, idx, last, temp; 1413 for (var p in data) { 1414 cur = result, prop = "", last = 0; 1415 do { 1416 idx = p.indexOf(".", last); 1417 temp = p.substring(last, idx !== -1 ? idx : undefined); 1418 cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {})); 1419 prop = temp; 1420 last = idx + 1; 1421 } while (idx >= 0); 1422 cur[prop] = data[p]; 1423 } 1424 return result[""]; 1425 1426 } 1427 1428 _json.prettyprint = function (json) { 1429 1430 return JSON.stringify(json, undefined, 2); 1431 1432 } 1433 1434 ////////////////////////////////////////// 1435 // wQuery (mini replacement for jQuery) // 1436 ////////////////////////////////////////// 1437 1438 var wQuery = function () { }; 1439 wQuery.constructor = wQuery; 1440 1441 wQuery.prototype.dom = function (selector, createOptions) { 1442 1443 var self = this, 1444 elements = []; 1445 1446 if (createOptions) { 1447 var element = document.createElement(selector); 1448 for (var k in createOptions) { 1449 element[k] = createOptions[k]; 1450 } 1451 } else { 1452 if (_w.isString(selector)) { 1453 elements = [].slice.call(document.querySelectorAll(selector)); 1454 } else { 1455 if (_w.isObject(selector) && selector.attributes) { elements = [selector]; } 1456 } 1457 self._elements = elements; 1458 self.length = elements.length; 1459 return self; 1460 } 1461 1462 } 1463 1464 wQuery.prototype.on = function (events, fn) { 1465 1466 var self = this, 1467 elements = self._elements; 1468 events = events.split(" "); 1469 for (var i = 0, lenEl = elements.length; i < lenEl; i++) { 1470 var element = elements[i]; 1471 for (var j = 0, lenEv = events.length; j < lenEv; j++) { 1472 if (element.addEventListener) { element.addEventListener(events[j], fn, false); } 1473 } 1474 } 1475 1476 } 1477 1478 wQuery.prototype.find = function (selector) { 1479 1480 var self = this, 1481 element = self.get(0), 1482 elements = []; 1483 1484 if (_w.isString(selector)) { 1485 elements = [].slice.call(element.querySelectorAll(selector)); 1486 } 1487 self._elements = elements; 1488 return self; 1489 1490 } 1491 1492 wQuery.prototype.get = function (index, chain) { 1493 1494 var self = this, 1495 elements = self._elements || [], 1496 element = elements[index] || {}; 1497 1498 if (chain) { 1499 self._element = element; 1500 return self; 1501 } else { 1502 return _w.isNumber(index) ? element : elements; 1503 } 1504 1505 } 1506 1507 wQuery.prototype.reverse = function () { 1508 this._elements = this._elements.reverse(); 1509 return this; 1510 } 1511 1512 wQuery.prototype.val = function (value) { 1513 return this.prop("value", value); 1514 } 1515 1516 wQuery.prototype.type = function (value) { 1517 return this.prop("type", value); 1518 } 1519 1520 wQuery.prototype.html = function (value) { 1521 return this.prop("innerHTML", value); 1522 } 1523 1524 wQuery.prototype.text = function (value) { 1525 return this.prop("innerHTML", escapeHTML(value)); 1526 } 1527 1528 wQuery.prototype.prop = function (prop, value) { 1529 1530 var self = this, 1531 elements = self._elements; 1532 1533 for (var i in elements) { 1534 if (_w.isUndefined(value)) { 1535 return elements[i][prop]; 1536 } else { 1537 elements[i][prop] = value; 1538 } 1539 } 1540 1541 } 1542 1543 wQuery.prototype.attr = function (attr, value) { 1544 1545 var self = this, 1546 elements = self._elements; 1547 for (var i in elements) { 1548 if (value === undefined) { 1549 return elements[i].getAttribute(attr); 1550 } else { 1551 elements[i].setAttribute(attr, value); 1552 } 1553 } 1554 return self; 1555 1556 } 1557 1558 wQuery.prototype.removeAttr = function (attr) { 1559 var self = this; 1560 for (var i in self._elements) self._elements[i].removeAttribute(attr); 1561 return self; 1562 } 1563 1564 wQuery.prototype.addClass = function (c) { 1565 var self = this; 1566 for (var i in self._elements) self._elements[i].classList.add(c); 1567 return self; 1568 } 1569 1570 wQuery.prototype.removeClass = function (c) { 1571 var self = this; 1572 for (var i in self._elements) self._elements[i].classList.remove(c); 1573 return self; 1574 } 1575 1576 wQuery.prototype.parents = function (selector) { 1577 var self = this, 1578 element = self.get(0), 1579 parent = element.parentNode, 1580 parents = []; 1581 1582 while (parent !== null) { 1583 var o = parent, 1584 matches = matchesSelector(o, selector), 1585 isNotDomRoot = (o.doctype === undefined) ? true : false; 1586 if (!selector) { matches = true; } 1587 if (matches && isNotDomRoot) { parents.push(o); } 1588 parent = o.parentNode; 1589 } 1590 self._elements = parents; 1591 return self; 1592 } 1593 1594 wQuery.prototype.parent = function (selector) { 1595 var self = this, 1596 element = self.get(0), 1597 o = element.parentNode, 1598 matches = matchesSelector(o, selector); 1599 if (!selector) { matches = true; } 1600 return matches ? o : {}; 1601 } 1602 1603 wQuery.prototype.clone = function (chain) { 1604 var self = this, 1605 element = self.get(0), 1606 clone = element.cloneNode(true); 1607 self._elements = [clone]; 1608 return chain ? self : clone; 1609 } 1610 1611 wQuery.prototype.empty = function (chain) { 1612 var self = this, 1613 element = self.get(0); 1614 if (!element || !element.hasChildNodes) { return chain ? self : element; } 1615 1616 while (element.hasChildNodes()) { 1617 element.removeChild(element.lastChild); 1618 } 1619 return chain ? self : element; 1620 } 1621 1622 wQuery.prototype.replaceWith = function (newDOM) { 1623 var self = this, 1624 oldDOM = self.get(0), 1625 parent = oldDOM.parentNode; 1626 parent.replaceChild(newDOM, oldDOM); 1627 } 1628 1629 wQuery.prototype.ready = function (callback) { 1630 1631 if (document && _w.isFunction(document.addEventListener)) { 1632 document.addEventListener("DOMContentLoaded", callback, false); 1633 } else if (window && _w.isFunction(window.addEventListener)) { 1634 window.addEventListener("load", callback, false); 1635 } else { 1636 document.onreadystatechange = function () { 1637 if (document.readyState === "complete") { callback(); } 1638 } 1639 } 1640 1641 } 1642 1643 ////////////////////// 1644 // WATCH DOM EVENTS // 1645 ////////////////////// 1646 1647 way = new WAY(); 1648 1649 var timeoutInput = null; 1650 var eventInputChange = function (e) { 1651 if (timeoutInput) { clearTimeout(timeoutInput); } 1652 timeoutInput = setTimeout(function () { 1653 var element = w.dom(e.target).get(0); 1654 way.dom(element).toStorage(); 1655 }, way.options.timeout); 1656 } 1657 1658 var eventClear = function (e) { 1659 e.preventDefault(); 1660 var options = way.dom(this).getOptions(); 1661 way.remove(options.data, options); 1662 } 1663 1664 var eventPush = function (e) { 1665 e.preventDefault(); 1666 var options = way.dom(this).getOptions(); 1667 if (!options || !options["action-push"]) { return false; } 1668 var split = options["action-push"].split(":"), 1669 selector = split[0] || null, 1670 value = split[1] || null; 1671 way.push(selector, value, options); 1672 } 1673 1674 var eventRemove = function (e) { 1675 e.preventDefault(); 1676 var options = way.dom(this).getOptions(); 1677 if (!options || !options["action-remove"]) { return false; } 1678 way.remove(options["action-remove"], options); 1679 } 1680 1681 var timeoutDOM = null; 1682 var eventDOMChange = function () { 1683 1684 // We need to register dynamically added bindings so we do it by watching DOM changes 1685 // We use a timeout since "DOMSubtreeModified" gets triggered on every change in the DOM (even input value changes) 1686 // so we can limit the number of scans when a user is typing something 1687 if (timeoutDOM) { clearTimeout(timeoutDOM); } 1688 timeoutDOM = setTimeout(function () { 1689 way.registerDependencies(); 1690 setEventListeners(); 1691 }, way.options.timeoutDOM); 1692 1693 } 1694 1695 ////////////// 1696 // INITIATE // 1697 ////////////// 1698 1699 w = new wQuery(); 1700 way.w = w; 1701 1702 var setEventListeners = function () { 1703 1704 w.dom("body").on("DOMSubtreeModified", eventDOMChange); 1705 w.dom("[" + tagPrefix + "-data]").on("input change", eventInputChange); 1706 w.dom("[" + tagPrefix + "-clear]").on("click", eventClear); 1707 w.dom("[" + tagPrefix + "-action-remove]").on("click", eventRemove); 1708 w.dom("[" + tagPrefix + "-action-push]").on("click", eventPush); 1709 1710 } 1711 1712 var eventInit = function () { 1713 1714 setEventListeners(); 1715 way.restore(); 1716 way.setDefaults(); 1717 way.registerDependencies(); 1718 way.updateDependencies(); 1719 1720 } 1721 1722 w.ready(eventInit); 1723 1724 return way; 1725 1726 }));
修复,绑定null值。
deepJSON方法增加null判断。null 转‘’。