way.js

Posted 淘小人官网

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了way.js相关的知识,希望对你有一定的参考价值。

技术分享
   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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") : 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(/"[^"\\\n\r]*"|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 }));
View Code

 

修复,绑定null值。

deepJSON方法增加null判断。null 转‘’。

以上是关于way.js的主要内容,如果未能解决你的问题,请参考以下文章

text 来自https://stackoverflow.com/questions/15492857/any-way-to-get-a-bounding-box-from-a-three-js-ob

微信小程序代码片段

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

VSCode自定义代码片段——.vue文件的模板