text: (function(root, factory) {
      if (typeof define === 'function' && define.amd) {
          define(factory);
      } else if (typeof exports === 'object') {
          module.exports = factory();
      } else {
          root.way = factory();
      }
  })(this, function() {
      'use strict';
      var way, w, tagPrefix = 'way';
  
      //////////////////////////////
      // EVENT EMITTER DEFINITION //
      //////////////////////////////
  
      var EventEmitter = function() {
          this._watchers = {};
          this._watchersAll = {};
      };
  
      EventEmitter.prototype.constructor = EventEmitter;
  
      EventEmitter.prototype.watchAll = function(handler) {
          this._watchersAll = this._watchersAll || [];
          if (!_w.contains(this._watchersAll, handler)) {
              this._watchersAll.push(handler);
          }
      };
  
      EventEmitter.prototype.watch = function(selector, handler) {
          if (!this._watchers) {
              this._watchers = {};
          }
          this._watchers[selector] = this._watchers[selector] || [];
          this._watchers[selector].push(handler);
      };
  
      EventEmitter.prototype.findWatcherDeps = function(selector) {
          // Go up to look for parent watchers
          // ex: if "some.nested.value" is the selector, it should also trigger for "some"
  
          var result = [];
          var watchers = _w.keys(this._watchers);
          watchers.forEach(function(watcher) {
              if (startsWith(selector, watcher)) {
                  result.push(watcher);
              }
          });
          return result;
      };
  
      EventEmitter.prototype.emitChange = function(selector /* , arguments */) {
          if (!this._watchers) {
              this._watchers = {};
          }
  
          var self = this;
  
          // Send data down to the local watchers
          var deps = self.findWatcherDeps(selector);
          deps.forEach(function(item) {
              if (self._watchers[item]) {
                  self._watchers[item].forEach(function(handler) {
                      handler.apply(self, [self.get(item)]);
                  });
              }
          });
  
          // Send data down to the global watchers
          if (!self._watchersAll || !_w.isArray(self._watchersAll)) {
              return;
          }
          self._watchersAll.forEach(function(watcher) {
              if (_w.isFunction(watcher)) {
                  watcher.apply(self, [selector, self.get(selector)]);
              }
          });
      };
  
      ////////////////////
      // WAY DEFINITION //
      ////////////////////
  
      var WAY = function() {
          this.data = {};
          this._bindings = {};
          this.options = {
              debug: false,
              persistent: true,
              timeoutInput: 50,
              timeoutDOM: 500
          };
      };
  
      // Inherit from EventEmitter
      WAY.prototype = Object.create(EventEmitter.prototype);
      WAY.constructor = WAY;
  
      //////////////////////////
      // DOM METHODS CHAINING //
      //////////////////////////
  
      WAY.prototype.dom = function(element) {
          this._element = w.dom(element).get(0);
          return this;
      };
  
      //////////////////////////////
      // DOM METHODS: DOM -> JSON //
      //////////////////////////////
  
      WAY.prototype.toStorage = function(options, element) {
          var self = this,
              element = element || self._element,
              options = options || self.dom(element).getOptions(),
              data = self.dom(element).toJSON(options),
              scope = self.dom(element).scope(),
              selector = scope ? scope + '.' + options.data : options.data;
  
          if (options.readonly) {
              return false;
          }
          self.set(selector, data, options);
      };
  
      WAY.prototype.toJSON = function(options, element) {
          var self = this,
              element = element || self._element,
              data = self.dom(element).getValue(),
              options = options || self.dom(element).getOptions();
  
          if (_w.isArray(options.pick)) {
              data = selectNested(data, options.pick, true);
          }
          if (_w.isArray(options.omit)) {
              data = selectNested(data, options.omit, false);
          }
  
          return data;
      };
  
      //////////////////////////////
      // DOM METHODS: JSON -> DOM //
      //////////////////////////////
  
      WAY.prototype.fromStorage = function(options, element) {
          var self = this,
              element = element || self._element,
              options = options || self.dom(element).getOptions();
  
          if (options.writeonly) {
              return false;
          }
  
          var scope = self.dom(element).scope();
          var selector = '';
          if (options.attr) {
              selector = scope ? scope : '';
          } else {
              selector = scope ? scope + '.' + options.data : options.data;
          }
  
          var data = self.get(selector);
  
          self.dom(element).fromJSON(data, options);
      };
  
      WAY.prototype.fromJSON = function(data, options, element) {
          var self = this,
              element = element || self._element,
              options = options || self.dom(element).getOptions();
  
          if (options.writeonly) {
              return false;
          }
  
          if (_w.isObject(data)) {
              if (_w.isArray(options.pick)) {
                  data = selectNested(data, options.pick, true);
              }
              if (_w.isArray(options.omit)) {
                  data = selectNested(data, options.omit, false);
              }
              var currentData = _w.isObject(self.dom(element).toJSON())
                  ? self.dom(element).toJSON()
                  : {};
              data = _w.extend(currentData, data);
          }
  
          if (options.json) {
              data = _json.isStringified(data) ? data : _json.prettyprint(data);
          }
  
          if (options.attr) {
              self.dom(element).setAttr(data, options);
          }
          if (options.data) {
              self.dom(element).setValue(data, options);
          }
      };
  
      /////////////////////////////////
      // DOM METHODS: GET - SET HTML //
      /////////////////////////////////
  
      WAY.prototype.getValue = function(element) {
          var self = this, element = element || self._element;
  
          var getters = {
              SELECT: function() {
                  return w.dom(element).val();
              },
              INPUT: function() {
                  var type = w.dom(element).type();
                  if (_w.contains(['text', 'password', 'number'], type)) {
                      return w.dom(element).val();
                  }
                  if (_w.contains(['checkbox', 'radio'], type)) {
                      return w.dom(element).prop('checked') ? w.dom(element).val() : null;
                  }
                  // by default
                  return w.dom(element).val();
              },
              TEXTAREA: function() {
                  return w.dom(element).val();
              }
          };
          var defaultGetter = function(a) {
              return w.dom(element).html();
          };
  
          var elementType = w.dom(element).get(0).tagName;
          var getter = getters[elementType] || defaultGetter;
          return getter();
      };
  
      WAY.prototype._transforms = {
          uppercase: function(data) {
              return _w.isString(data) ? data.toUpperCase() : data;
          },
          lowercase: function(data) {
              return _w.isString(data) ? data.toLowerCase() : data;
          },
          reverse: function(data) {
              return data && data.split && _w.isFunction(data.split)
                  ? data.split('').reverse().join('')
                  : data;
          },
          toInt: function (data) {
              if (typeof data == 'undefined') {
                  return 0;
              }
              return data.toFixed(0);
          },
          roundUp: function (data) {
              if (typeof data == 'undefined') {
                  return 0;
              }
              return Math.ceil(data);
          }
  
      };
  
      WAY.prototype.registerTransform = function(name, transform) {
          var self = this;
          if (_w.isFunction(transform)) {
              self._transforms[name] = transform;
          }
      };
  
      WAY.prototype.setAttr = function(data, options, element) {
  
          var self = this,
              element = element || self._element,
              options = options || self.dom(element).getOptions();
  
          var optionsKeys = Object.keys(options);
          var validAttributes = {};
          var tmpkey;
  
          for (var i = 0; i < optionsKeys.length; i++) {
              if (optionsKeys[i].indexOf('attr-') === 0) {
                  tmpkey = optionsKeys[i].replace(/^attr-/, '');
                  if (typeof validAttributes[tmpkey] === 'undefined') {
                      validAttributes[tmpkey] = {'value': null, 'default': null}
                  }
                  validAttributes[tmpkey]['value'] = options[optionsKeys[i]];
              } else if (optionsKeys[i].indexOf('default-attr-') === 0) {
                  tmpkey = optionsKeys[i].replace(/^default-attr-/, '');
                  if (typeof validAttributes[tmpkey] === 'undefined') {
                      validAttributes[tmpkey] = {'value': null, 'default': null}
                  }
                  validAttributes[tmpkey]['default'] = options[optionsKeys[i]]
              }
          }
  
          optionsKeys = Object.keys(validAttributes);
          for (var i = 0; i < optionsKeys.length; i++) {
              var value = null;
              if (validAttributes[optionsKeys[i]]['value']) {
                  // get from storage
                  var scope = way.dom(element).scope();
                  var key = scope ? scope + '.' + validAttributes[optionsKeys[i]]['value'] : validAttributes[optionsKeys[i]]['value'];
                  value = way.get(key);
              }
              if ((value === null ||  value === undefined)) {
                  if (validAttributes[optionsKeys[i]]['default']) {
                      value = validAttributes[optionsKeys[i]]['default'];
                  } else if (validAttributes[optionsKeys[i]]['value']) {
                      value = validAttributes[optionsKeys[i]]['value'];
                  }
              }
  
              if (value !== null) {
                  w.dom(element).attr(optionsKeys[i], value)
              }
          }
      }
  
      WAY.prototype.setValue = function(data, options, element) {
          var self = this,
              element = element || self._element,
              options = options || self.dom(element).getOptions();
  
          var templatevar = options.templatevar || 'data';
          templatevar = '{' + templatevar + '}'
  
          options.transform = options.transform || [];
          options.transform.forEach(function(transformName) {
              var transform =
                  self._transforms[transformName] ||
                  function(data, el) {
                      return data;
                  };
              data = transform(data, w.dom(element));
          });
  
          var setters = {
              SELECT: function(a) {
                  w.dom(element).val(a);
              },
              INPUT: function(a) {
                  if (!_w.isString(a)) {
                      a = JSON.stringify(a);
                  }
                  var type = w.dom(element).get(0).type;
                  if (_w.contains(['text', 'password', 'number'], type)) {
                      w.dom(element).val(a || '');
                  }
                  if (_w.contains(['checkbox', 'radio'], type)) {
                      if (a === w.dom(element).val()) {
                          w.dom(element).prop('checked', true);
                      } else {
                          w.dom(element).prop('checked', false);
                      }
                  }
                  // by default
                  w.dom(element).val(a || '');
              },
              TEXTAREA: function(a) {
                  if (!_w.isString(a)) {
                      a = JSON.stringify(a);
                  }
                  if (options.template) {
                      var val = w.dom(element).val().replace(templatevar, a || '');
                      w.dom(element).val(val);
                  } else {
                      w.dom(element).val(a || '');
                  }
              },
              PRE: function(a) {
                  if (options.html) {
                      if (options.template) {
                          a = w.dom(element).html().replace(templatevar, a);
                      }
                      w.dom(element).html(a);
                  } else {
                      if (options.template) {
                          a = w.dom(element).text().replace(templatevar, a);
                      }
                      w.dom(element).text(a);
                  }
              },
              // support binding a.href by `way-href="data.a.href"`
              "A": function(a) {
                  if (options.href) {
                      var scope,key,val;
                      scope = way.dom(element).scope();
                      key = scope ? scope + '.' + options.href : options.href;
                      val = way.get(key);
                      w.dom(element).attr("href", val);
                  }
  
                  if (options.html) {
                      if (options.template) {
                          a = w.dom(element).html().replace(templatevar, a);
                      }
                      w.dom(element).html(a);
                  } else {
                      if (options.template) {
                          a = w.dom(element).text().replace(templatevar, a);
                      }
                      w.dom(element).text(a);
                  }
              },
  
              IMG: function(a) {
                  if (!a) {
                      a = options.default || '';
                      w.dom(element).attr('src', a);
                      return false;
                  }
  
                  var isValidImageUrl = function(url, cb) {
                      w.dom(element).addClass('way-loading');
                      w.dom('img', {
                          src: url,
                          onerror: function() {
                              cb(false);
                          },
                          onload: function() {
                              cb(true);
                          }
                      });
                  };
  
                  if (options.template) {
                      a = w.dom(element).attr('src').replace(templatevar, a);
                  }
  
                  isValidImageUrl(a, function(response) {
                      w.dom(element).removeClass('way-loading');
                      if (response) {
                          w.dom(element).removeClass('way-error').addClass('way-success');
                      } else {
                          if (a) {
                              w.dom(element).addClass('way-error');
                          } else {
                              w
                                  .dom(element)
                                  .removeClass('way-error')
                                  .removeClass('way-success');
                          }
                          a = options.default || '';
                      }
                      w.dom(element).attr('src', a);
                  });
              }
          };
  
          var defaultSetter = function(a) {
              if (options.html) {
                  if (options.template) {
                      a = w.dom(element).html().replace(templatevar, a);
                  }
                  w.dom(element).html(a);
              } else {
                  if (options.template) {
                      a = w.dom(element).text().replace(templatevar, a);
                  }
                  w.dom(element).text(a);
              }
          };
  
          var elementType = w.dom(element).get(0).tagName;
          var setter = setters[elementType] || defaultSetter;
          setter(data);
      };
  
      WAY.prototype.setDefault = function(force, options, element) {
          var self = this,
              element = element || self._element,
              force = force || false,
              options = options
                  ? _w.extend(self.dom(element).getOptions(), options)
                  : self.dom(element).getOptions();
  
          // Should we just set the default value in the DOM, or also in the datastore?
          if (!options.default) {
              return false;
          }
          if (force) {
              self.set(options.data, options.default, options);
          } else {
              self.dom(element).setValue(options.default, options);
          }
      };
  
      WAY.prototype.setDefaults = function() {
          var self = this, dataSelector = '[' + tagPrefix + '-default]';
  
          var elements = w.dom(dataSelector).get();
          for (var i in elements) {
              var element = elements[i],
                  options = self.dom(element).getOptions(),
                  selector = options.data || null,
                  data = selector ? self.get(selector) : null;
              if (!data) {
                  self.dom(element).setDefault();
              }
          }
      };
  
      /////////////////////////////////////
      // DOM METHODS: GET - SET BINDINGS //
      /////////////////////////////////////
  
      // Scans the DOM to look for new bindings
      WAY.prototype.registerBindings = function() {
          // Dealing with bindings removed from the DOM by just resetting all the bindings all the time.
          // Isn't there a better way?
          // One idea would be to add a "way-bound" class to bound elements
          // self._bindings = {};
  
          var self = this;
          var selector = '[' + tagPrefix + '-data], [' + tagPrefix + '-attr]';
          self._bindings = {};
  
          var elements = w.dom(selector).get();
          for (var i in elements) {
              var element = elements[i];
              var options = self.dom(element).getOptions();
              var scope = self.dom(element).scope();
  
              var selector = '';
              if (options.attr) {
                  selector = scope ? scope : '';
              } else {
                  selector = scope ? scope + '.' + options.data : options.data;
              }
  
              self._bindings[selector] = self._bindings[selector] || [];
              if (!_w.contains(self._bindings[selector], w.dom(element).get(0))) {
                  self._bindings[selector].push(w.dom(element).get(0));
              }
          }
      };
  
      WAY.prototype.updateBindings = function(selector) {
          var self = this;
          self._bindings = self._bindings || {};
  
          // Set bindings for the data selector
          var bindings = pickAndMergeParentArrays(self._bindings, selector);
          bindings.forEach(function(element) {
              var focused = w.dom(element).get(0) === w.dom(':focus').get(0)
                  ? true
                  : false;
  
              if (!focused) {
                  self.dom(element).fromStorage();
              }
          });
  
          // Set bindings for the global selector
          if (self._bindings['__all__']) {
              self._bindings['__all__'].forEach(function(element) {
                  self.dom(element).fromJSON(self.data);
              });
          }
      };
  
      ////////////////////////////////////
      // DOM METHODS: GET - SET REPEATS //
      ////////////////////////////////////
  
      WAY.prototype.registerRepeats = function() {
          // Register repeats
          var self = this;
          var selector = '[' + tagPrefix + '-repeat]';
          self._repeats = self._repeats || {};
          self._repeatsCount = self._repeatsCount || 0;
  
          var elements = w.dom(selector).get();
          for (var i in elements) {
              var element = elements[i];
              var options = self.dom(element).getOptions();
              var scope = self.dom(element).scope();
              var repeat_selector = scope ? scope + '.' + options.repeat : options.repeat;
  
              self._repeats[repeat_selector] = self._repeats[repeat_selector] || [];
  
              var wrapperAttr =
                  tagPrefix + '-repeat-wrapper="' + self._repeatsCount + '"',
                  parent = w.dom(element).parent('[' + wrapperAttr + ']');
              if (!parent.length) {
                  self._repeats[repeat_selector].push({
                      id: self._repeatsCount,
                      element: w
                          .dom(element)
                          .clone(true)
                          .removeAttr(tagPrefix + '-repeat')
                          .removeAttr(tagPrefix + '-filter')
                          .get(0),
                      selector: repeat_selector,
                      filter: options.filter
                  });
  
                  var wrappertag = options.wrappertag || "div";
                  // use parent as wrapper
                  if (wrappertag === 'parent') {
                      var wrapper = w.dom(element).parent();
                      w.dom(wrapper).attr(tagPrefix + '-repeat-wrapper', self._repeatsCount);
                      w.dom(wrapper).attr(tagPrefix + '-scope', options.repeat);
                      if (options.filter) {
                          w.dom(wrapper).attr(tagPrefix + '-filter', options.filter);
                      }
                  } else {
                      var wrapper = document.createElement(wrappertag);
                      w.dom(wrapper).attr(tagPrefix + '-repeat-wrapper', self._repeatsCount);
                      w.dom(wrapper).attr(tagPrefix + '-scope', options.repeat);
                      if (options.filter) {
                          w.dom(wrapper).attr(tagPrefix + '-filter', options.filter);
                      }
  
                      w.dom(element).replaceWith(wrapper);
                  }
  
                  self.updateRepeats(repeat_selector);
  
                  self._repeatsCount++;
              }
          }
      };
  
      /*
        WAY.prototype._filters = {
            noFalsy: function(item ) {
                if (!item) {
                    return false;
                } else {
                    return true;
                }
            }
        };
  
        WAY.prototype.registerFilter = function(name, filter) {
            var self = this;
            if (_w.isFunction(filter)) { self._filters[name] = filter; }
        }
        */
  
      WAY.prototype.updateRepeats = function(selector) {
          var self = this;
          self._repeats = self._repeats || {};
  
          var repeats = pickAndMergeParentArrays(self._repeats, selector);
  
          repeats.forEach(function(repeat) {
              var wrapper = '[' + tagPrefix + '-repeat-wrapper="' + repeat.id + '"]';
              var data = self.get(repeat.selector);
              var items = [];
  
              repeat.filter = repeat.filter || [];
              w.dom(wrapper).empty();
              var repeatOptions = self.dom(repeat.element).getOptions();
  
              if (way.options.debug && repeatOptions.debugdump) {
                  console.log("[WAY REPEAT DUMP]");
                  console.log("Options");
                  console.table(repeatOptions);
                  // console.log("Data");
                  // console.log(data);
              }
  
              for (var key in data) {
                  /*
                  var item = data[key],
                          test = true;
                  for (var i in repeat.filter) {
                      var filterName = repeat.filter[i];
                      var filter = self._filters[filterName] || function(data) { return data };
                      test = filter(item);
                      if (!test) { break; }
                  }
                  if (!test) { continue; }
                  */
  
                  if (way.options.debug && repeatOptions.debugdump) {
                      console.log("[WAY REPEAT Dump Row]")
                      console.table([{"key": key, "data": data[key]}]);
                  }
  
                  w.dom(repeat.element).attr(tagPrefix + '-scope', key);
                  var html = w.dom(repeat.element).get(0).outerHTML;
  
                  // html = html.replace(/\$\$key/gi, key);
                  // html = html.replace(/\$\$data/gi, JSON.stringify(data[key]));
                  html = html.replaceAll('$$' + repeatOptions.repeatkey, key);
                  html = html.replaceAll('$$' + repeatOptions.repeatdata, JSON.stringify(data[key]));
                  items.push(html);
              }
  
              w.dom(wrapper).html(items.join(''));
              self.registerBindings();
              self.updateBindings();
          });
      };
  
      ////////////////////////
      // DOM METHODS: FORMS //
      ////////////////////////
  
      WAY.prototype.updateForms = function() {
          // If we just parse the forms with form2js (see commits before 08/19/2014) and set the data with way.set(),
          // we reset the entire data for this pathkey in the datastore. It causes the bug
          // reported here: https://github.com/gwendall/way.js/issues/10
          // Solution:
          // 1. watch new forms with a [way-data] attribute
          // 2. remove this attribute
          // 3. attach the appropriate attributes to its child inputs
          // -> so that each input is set separately to way.js' datastore
  
          var self = this;
          var selector = 'form[' + tagPrefix + '-data]';
  
          var elements = w.dom(selector).get();
          for (var i in elements) {
              var form = elements[i],
                  options = self.dom(form).getOptions(),
                  formDataSelector = options.data;
              w.dom(form).removeAttr(tagPrefix + '-data');
  
              // Reverse needed to set the right index for "[]" names
              var inputs = w.dom(form).find('[name]').reverse().get();
              for (var i in inputs) {
                  var input = inputs[i], name = w.dom(input).attr('name');
  
                  if (endsWith(name, '[]')) {
                      var array = name.split('[]')[0],
                          arraySelector = "[name^='" + array + "']",
                          arrayIndex = w.dom(form).find(arraySelector).get().length;
                      name = array + '.' + arrayIndex;
                  }
                  var selector = formDataSelector + '.' + name;
                  options.data = selector;
                  self.dom(input).setOptions(options);
                  w.dom(input).removeAttr('name');
              }
          }
      };
  
      /////////////////////////////////////////////
      // DOM METHODS: GET - SET ALL DEPENDENCIES //
      /////////////////////////////////////////////
  
      WAY.prototype.registerDependencies = function() {
          this.registerBindings();
          this.registerRepeats();
      };
  
      WAY.prototype.updateDependencies = function(selector) {
          this.updateBindings(selector);
          this.updateRepeats(selector);
          this.updateForms(selector);
      };
  
      //////////////////////////////////
      // DOM METHODS: OPTIONS PARSING //
      //////////////////////////////////
  
      WAY.prototype.setOptions = function(options, element) {
          var self = this, element = self._element || element;
  
          for (var k in options) {
              var attr = tagPrefix + '-' + k, value = options[k];
              w.dom(element).attr(attr, value);
          }
      };
  
      WAY.prototype.getOptions = function(element) {
          var self = this,
              element = element || self._element,
              defaultOptions = {
                  data: null,
                  html: false,
                  readonly: false,
                  writeonly: false,
                  persistent: false,
                  attr: false,
                  template: false,
                  templatevar: 'data',
                  debug: false,
                  debugdump: false,
                  repeatkey: 'key',
                  repeatdata: 'data'
              };
          return _w.extend(defaultOptions, self.dom(element).getAttrs(tagPrefix));
      };
  
      WAY.prototype.getAttrs = function(prefix, element) {
          var self = this, element = element || self._element;
  
          var parseAttrValue = function(key, value) {
              var attrTypes = {
                  pick: 'array',
                  omit: 'array',
                  readonly: 'boolean',
                  writeonly: 'boolean',
                  json: 'boolean',
                  html: 'boolean',
                  persistent: 'boolean',
                  attr: 'boolean',
                  template: 'boolean',
                  debugdump: 'boolean'
              };
  
              var parsers = {
                  array: function(value) {
                      return value.split(',');
                  },
                  boolean: function(value) {
                      if (value === 'true') {
                          return true;
                      }
                      if (value === 'false') {
                          return false;
                      }
                      return true;
                  }
              };
              var defaultParser = function() {
                  return value;
              };
              var valueType = attrTypes[key] || null;
              var parser = parsers[valueType] || defaultParser;
  
              return parser(value);
          };
  
          var attributes = {};
          var attrs = [].slice.call(w.dom(element).get(0).attributes);
          attrs.forEach(function(attr) {
              var include = prefix && startsWith(attr.name, prefix + '-')
                  ? true
                  : false;
              if (include) {
                  var name = prefix
                      ? attr.name.slice(prefix.length + 1, attr.name.length)
                      : attr.name;
                  var value = parseAttrValue(name, attr.value);
                  if (_w.contains(['transform', 'filter'], name)) {
                      value = value.split('|');
                  }
                  attributes[name] = value;
              }
          });
  
          return attributes;
      };
  
      //////////////////////////
      // DOM METHODS: SCOPING //
      //////////////////////////
  
      WAY.prototype.scope = function(options, element) {
          var self = this,
              element = element || self._element,
              scopeAttr = tagPrefix + '-scope',
              scopeBreakAttr = tagPrefix + '-scope-break',
              scopes = [],
              scope = '';
  
          var parentsSelector = '[' + scopeBreakAttr + '], [' + scopeAttr + ']';
          var elements = w.dom(element).parents(parentsSelector).get();
  
          for (var i in elements) {
              var el = elements[i];
              if (w.dom(el).attr(scopeBreakAttr)) {
                  break;
              }
              var attr = w.dom(el).attr(scopeAttr);
              scopes.unshift(attr);
          }
  
          if (w.dom(element).attr(scopeAttr)) {
              scopes.push(w.dom(element).attr(scopeAttr));
          }
          if (w.dom(element).attr(scopeBreakAttr)) {
              scopes = [];
          }
  
          scope = _w.compact(scopes).join('.');
  
          return scope;
      };
  
      //////////////////
      // DATA METHODS //
      //////////////////
  
      WAY.prototype.get = function(selector) {
          var self = this;
          if (selector !== undefined && !_w.isString(selector)) {
              return false;
          }
          if (!self.data) {
              return {};
          }
          return selector ? _json.get(self.data, selector) : self.data;
      };
  
      WAY.prototype.set = function(selector, value, options) {
          if (!selector) {
              return false;
          }
          if (selector.split('.')[0] === 'this') {
              console.log('Sorry, "this" is a reserved word in way.js');
              return false;
          }
  
          var self = this;
          options = options || {};
  
          if (selector) {
              if (!_w.isString(selector)) {
                  return false;
              }
              self.data = self.data || {};
              self.data = selector ? _json.set(self.data, selector, value) : {};
  
              self.updateDependencies(selector);
              self.emitChange(selector, value);
              if (options.persistent) {
                  self.backup(selector);
              }
          }
      };
  
      WAY.prototype.push = function(selector, value, options) {
          if (!selector) {
              return false;
          }
  
          var self = this;
          options = options || {};
  
          if (selector) {
              self.data = selector ? _json.push(self.data, selector, value, true) : {};
          }
  
          self.updateDependencies(selector);
          self.emitChange(selector, null);
          if (options.persistent) {
              self.backup(selector);
          }
      };
  
      WAY.prototype.remove = function(selector, options) {
          var self = this;
          options = options || {};
  
          if (selector) {
              self.data = _json.remove(self.data, selector);
          } else {
              self.data = {};
          }
  
          self.updateDependencies(selector);
          self.emitChange(selector, null);
          if (options.persistent) {
              self.backup(selector);
          }
      };
  
      WAY.prototype.clear = function() {
          this.remove(null, { persistent: true });
      };
  
      //////////////////////////
      // LOCALSTORAGE METHODS //
      //////////////////////////
  
      WAY.prototype.backup = function() {
          var self = this;
          if (!self.options.persistent) {
              return;
          }
          try {
              var data = self.data || {};
              localStorage.setItem(tagPrefix, JSON.stringify(data));
          } catch (e) {
              console.log('Your browser does not support localStorage.');
          }
      };
  
      WAY.prototype.restore = function() {
          var self = this;
          if (!self.options.persistent) {
              return;
          }
          try {
              var data = localStorage.getItem(tagPrefix);
              try {
                  data = JSON.parse(data);
                  for (var key in data) {
                      self.set(key, data[key]);
                  }
              } catch (e) {}
          } catch (e) {
              console.log('Your browser does not support localStorage.');
          }
      };
  
      //////////
      // MISC //
      //////////
  
      var matchesSelector = function(el, selector) {
          var matchers = [
                  'matches',
                  'matchesSelector',
                  'webkitMatchesSelector',
                  'mozMatchesSelector',
                  'msMatchesSelector',
                  'oMatchesSelector'
              ],
              fn = null;
          for (var i in matchers) {
              fn = matchers[i];
              if (_w.isFunction(el[fn])) {
                  return el[fn](selector);
              }
          }
          return false;
      };
  
      var startsWith = function(str, starts) {
          if (starts === '') {
              return true;
          }
          if (str === null || starts === null) {
              return false;
          }
          str = String(str);
          starts = String(starts);
          return (
              str.length >= starts.length && str.slice(0, starts.length) === starts
          );
      };
  
      var endsWith = function(str, ends) {
          if (ends === '') {
              return true;
          }
          if (str === null || ends === null) {
              return false;
          }
          str = String(str);
          ends = String(ends);
          return (
              str.length >= ends.length &&
              str.slice(str.length - ends.length, str.length) === ends
          );
      };
  
      var cleanEmptyKeys = function(object) {
          return _w.pick(object, _w.compact(_w.keys(object)));
      };
  
      var filterStartingWith = function(object, string, type) {
          // true: pick - false: omit
  
          var keys = _w.keys(object);
          keys.forEach(function(key) {
              if (type) {
                  if (!startsWith(key, string)) {
                      delete object[key];
                  }
              } else {
                  if (startsWith(key, string)) {
                      delete object[key];
                  }
              }
          });
          return object;
      };
  
      var selectNested = function(data, keys, type) {
          // true: pick - false: omit
  
          // Flatten / unflatten to allow for nested picks / omits (doesn't work with regular pick)
          // ex:  data = {something:{nested:"value"}}
          //		keys = ['something.nested']
  
          var flat = _json.flatten(data);
          for (var i in keys)
              flat = filterStartingWith(flat, keys[i], type);
          var unflat = _json.unflatten(flat);
          // Unflatten returns an object with an empty property if it is given an empty object
          return cleanEmptyKeys(unflat);
      };
  
      var pickAndMergeParentArrays = function(object, selector) {
          // Example:
          // object = { a: [1,2,3], a.b: [4,5,6], c: [7,8,9] }
          // fn(object, "a.b")
          // > [1,2,3,4,5,6]
  
          var keys = [];
          if (selector) {
              // Set bindings for the specified selector
  
              // (bindings that are repeat items)
              var split = selector.split('.'),
                  lastKey = split[split.length - 1],
                  isArrayItem = !isNaN(lastKey);
  
              if (isArrayItem) {
                  split.pop();
                  var key = split.join('.');
                  keys = object[key] ? _w.union(keys, object[key]) : keys;
              }
  
              // (bindings with keys starting with, to include nested bindings)
              for (var key in object) {
                  if (startsWith(key, selector)) {
                      keys = _w.union(keys, object[key]);
                  }
              }
          } else {
              // Set bindings for all selectors
              for (var key in object) {
                  keys = _w.union(keys, object[key]);
              }
          }
          return keys;
      };
  
      var isPrintableKey = function(e) {
          var keycode = e.keyCode;
          if (!keycode) {
              return true;
          }
  
          var valid =
              keycode === 8 || // delete
              (keycode > 47 && keycode < 58) || // number keys
              keycode === 32 ||
              keycode === 13 || // spacebar & return key(s) (if you want to allow carriage returns)
              (keycode > 64 && keycode < 91) || // letter keys
              (keycode > 95 && keycode < 112) || // numpad keys
              (keycode > 185 && keycode < 193) || // ;=,-./` (in order)
              (keycode > 218 && keycode < 223); // [\]' (in order)
  
          return valid;
      };
  
      var _escapeHTML = function(str) {
          return str && _w.isString(str)
              ? str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
              : str;
      };
  
      ///////////////////////////////////////////////////
      // _w (strip of the required underscore methods) //
      ///////////////////////////////////////////////////
  
      var _w = {};
  
      var ArrayProto = Array.prototype,
          ObjProto = Object.prototype,
          FuncProto = Function.prototype;
  
      var nativeIsArray = Array.isArray,
          nativeKeys = Object.keys,
          nativeBind = FuncProto.bind;
  
      var push = ArrayProto.push,
          slice = ArrayProto.slice,
          concat = ArrayProto.concat,
          toString = ObjProto.toString,
          hasOwnProperty = ObjProto.hasOwnProperty;
  
      var flatten = function(input, shallow, strict, output) {
          if (shallow && _w.every(input, _w.isArray)) {
              return concat.apply(output, input);
          }
          for (var i = 0, length = input.length; i < length; i++) {
              var value = input[i];
              if (!_w.isArray(value) && !_w.isArguments(value)) {
                  if (!strict) output.push(value);
              } else if (shallow) {
                  push.apply(output, value);
              } else {
                  flatten(value, shallow, strict, output);
              }
          }
          return output;
      };
  
      var createCallback = function(func, context, argCount) {
          if (context === void 0) return func;
          switch (argCount == null ? 3 : argCount) {
              case 1:
                  return function(value) {
                      return func.call(context, value);
                  };
              case 2:
                  return function(value, other) {
                      return func.call(context, value, other);
                  };
              case 3:
                  return function(value, index, collection) {
                      return func.call(context, value, index, collection);
                  };
              case 4:
                  return function(accumulator, value, index, collection) {
                      return func.call(context, accumulator, value, index, collection);
                  };
          }
          return function() {
              return func.apply(context, arguments);
          };
      };
  
      _w.compact = function(array) {
          return _w.filter(array, _w.identity);
      };
  
      _w.filter = function(obj, predicate, context) {
          var results = [];
          if (obj == null) return results;
          predicate = _w.iteratee(predicate, context);
          _w.each(obj, function(value, index, list) {
              if (predicate(value, index, list)) results.push(value);
          });
          return results;
      };
  
      _w.identity = function(value) {
          return value;
      };
  
      _w.every = function(obj, predicate, context) {
          if (obj == null) return true;
          predicate = _w.iteratee(predicate, context);
          var keys = obj.length !== +obj.length && _w.keys(obj),
              length = (keys || obj).length,
              index,
              currentKey;
          for (index = 0; index < length; index++) {
              currentKey = keys ? keys[index] : index;
              if (!predicate(obj[currentKey], currentKey, obj)) return false;
          }
          return true;
      };
  
      _w.union = function() {
          return _w.uniq(flatten(arguments, true, true, []));
      };
  
      _w.uniq = function(array, isSorted, iteratee, context) {
          if (array == null) return [];
          if (!_w.isBoolean(isSorted)) {
              context = iteratee;
              iteratee = isSorted;
              isSorted = false;
          }
          if (iteratee != null) iteratee = _w.iteratee(iteratee, context);
          var result = [];
          var seen = [];
          for (var i = 0, length = array.length; i < length; i++) {
              var value = array[i];
              if (isSorted) {
                  if (!i || seen !== value) result.push(value);
                  seen = value;
              } else if (iteratee) {
                  var computed = iteratee(value, i, array);
                  if (_w.indexOf(seen, computed) < 0) {
                      seen.push(computed);
                      result.push(value);
                  }
              } else if (_w.indexOf(result, value) < 0) {
                  result.push(value);
              }
          }
          return result;
      };
  
      _w.pick = function(obj, iteratee, context) {
          var result = {}, key;
          if (obj == null) return result;
          if (_w.isFunction(iteratee)) {
              iteratee = createCallback(iteratee, context);
              for (key in obj) {
                  var value = obj[key];
                  if (iteratee(value, key, obj)) result[key] = value;
              }
          } else {
              var keys = concat.apply([], slice.call(arguments, 1));
              obj = new Object(obj);
              for (var i = 0, length = keys.length; i < length; i++) {
                  key = keys[i];
                  if (key in obj) result[key] = obj[key];
              }
          }
          return result;
      };
  
      _w.has = function(obj, key) {
          return obj != null && hasOwnProperty.call(obj, key);
      };
  
      _w.keys = function(obj) {
          if (!_w.isObject(obj)) return [];
          if (nativeKeys) return nativeKeys(obj);
          var keys = [];
          for (var key in obj)
              if (_w.has(obj, key)) keys.push(key);
          return keys;
      };
  
      _w.contains = function(obj, target) {
          if (obj == null) return false;
          if (obj.length !== +obj.length) obj = _w.values(obj);
          return _w.indexOf(obj, target) >= 0;
      };
  
      _w.sortedIndex = function(array, obj, iteratee, context) {
          iteratee = _w.iteratee(iteratee, context, 1);
          var value = iteratee(obj);
          var low = 0, high = array.length;
          while (low < high) {
              var mid = (low + high) >>> 1;
              if (iteratee(array[mid]) < value) low = mid + 1;
              else high = mid;
          }
          return low;
      };
  
      _w.property = function(key) {
          return function(obj) {
              return obj[key];
          };
      };
  
      _w.iteratee = function(value, context, argCount) {
          if (value == null) return _w.identity;
          if (_w.isFunction(value)) return createCallback(value, context, argCount);
          if (_w.isObject(value)) return _w.matches(value);
          return _w.property(value);
      };
  
      _w.pairs = function(obj) {
          var keys = _w.keys(obj);
          var length = keys.length;
          var pairs = Array(length);
          for (var i = 0; i < length; i++) {
              pairs[i] = [keys[i], obj[keys[i]]];
          }
          return pairs;
      };
  
      _w.matches = function(attrs) {
          var pairs = _w.pairs(attrs), length = pairs.length;
          return function(obj) {
              if (obj == null) return !length;
              obj = new Object(obj);
              for (var i = 0; i < length; i++) {
                  var pair = pairs[i], key = pair[0];
                  if (pair[1] !== obj[key] || !(key in obj)) return false;
              }
              return true;
          };
      };
  
      _w.indexOf = function(array, item, isSorted) {
          if (array == null) return -1;
          var i = 0, length = array.length;
          if (isSorted) {
              if (typeof isSorted == 'number') {
                  i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
              } else {
                  i = _w.sortedIndex(array, item);
                  return array[i] === item ? i : -1;
              }
          }
          for (; i < length; i++)
              if (array[i] === item) return i;
          return -1;
      };
  
      _w.values = function(obj) {
          var keys = _w.keys(obj);
          var length = keys.length;
          var values = Array(length);
          for (var i = 0; i < length; i++) {
              values[i] = obj[keys[i]];
          }
          return values;
      };
  
      _w.extend = function(obj) {
          if (!_w.isObject(obj)) return obj;
          var source, prop;
          for (var i = 1, length = arguments.length; i < length; i++) {
              source = arguments[i];
              for (prop in source) {
                  if (hasOwnProperty.call(source, prop)) {
                      obj[prop] = source[prop];
                  }
              }
          }
          return obj;
      };
  
      _w.isArray = function(obj) {
          return toString.call(obj) === '[object Array]';
      };
  
      _w.isBoolean = function(obj) {
          return (
              obj === true || obj === false || toString.call(obj) === '[object Boolean]'
          );
      };
  
      _w.isUndefined = function(obj) {
          return obj === void 0;
      };
  
      _w.isObject = function(obj) {
          var type = typeof obj;
          return type === 'function' || (type === 'object' && !!obj);
      };
  
      _w.each = function(obj, iteratee, context) {
          if (obj == null) return obj;
          iteratee = createCallback(iteratee, context);
          var i, length = obj.length;
          if (length === +length) {
              for (i = 0; i < length; i++) {
                  iteratee(obj[i], i, obj);
              }
          } else {
              var keys = _w.keys(obj);
              for ((i = 0), (length = keys.length); i < length; i++) {
                  iteratee(obj[keys[i]], keys[i], obj);
              }
          }
          return obj;
      };
  
      _w.each(
          ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'],
          function(name) {
              _w['is' + name] = function(obj) {
                  return toString.call(obj) === '[object ' + name + ']';
              };
          }
      );
  
      ///////////////////////////////////////////////////////////
      // _json (strip of the required underscore.json methods) //
      ///////////////////////////////////////////////////////////
  
      var deepJSON = function(obj, key, value, remove) {
          var keys = key
                  .replace(/\[(["']?)([^\1]+?)\1?\]/g, '.$2')
                  .replace(/^\./, '')
                  .split('.'),
              root,
              i = 0,
              n = keys.length;
  
          // Set deep value
          if (arguments.length > 2) {
              root = obj;
              n--;
  
              while (i < n) {
                  key = keys[i++];
                  obj = obj[key] = _w.isObject(obj[key]) ? obj[key] : {};
              }
  
              if (remove) {
                  if (_w.isArray(obj)) {
                      obj.splice(keys[i], 1);
                  } else {
                      delete obj[keys[i]];
                  }
              } else {
                  obj[keys[i]] = value;
              }
  
              value = root;
  
              // Get deep value
          } else {
              while ((obj = obj[keys[i++]]) != null && i < n) {
              }
              value = i < n ? void 0 : obj;
          }
  
          return value;
      };
  
      var _json = {};
  
      _json.VERSION = '0.1.0';
      _json.debug = true;
  
      _json.exit = function(source, reason, data, value) {
          if (!_json.debug) return;
  
          var messages = {};
          messages.noJSON = 'Not a JSON';
          messages.noString = 'Not a String';
          messages.noArray = 'Not an Array';
          messages.missing = 'Missing argument';
  
          var error = { source: source, data: data, value: value };
          error.message = messages[reason]
              ? messages[reason]
              : 'No particular reason';
          console.log('Error', error);
          return;
      };
  
      _json.is = function(json) {
          return toString.call(json) == '[object Object]';
      };
  
      _json.isStringified = function(string) {
          var test = false;
          try {
              test = /^[\],:{}\s]*$/.test(
                  string
                      .replace(/\\["\\\/bfnrtu]/g, '@')
                      .replace(
                          /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
                          ']'
                      )
                      .replace(/(?:^|:|,)(?:\s*\[)+/g, '')
              );
          } catch (e) {}
          return test;
      };
  
      _json.get = function(json, selector) {
          if (json == undefined) return _json.exit('get', 'missing', 'json', json);
          if (selector == undefined)
              return _json.exit('get', 'missing', 'selector', selector);
          if (!_w.isString(selector))
              return _json.exit('get', 'noString', 'selector', selector);
          return deepJSON(json, selector);
      };
  
      _json.set = function(json, selector, value) {
          if (json == undefined) return _json.exit('set', 'missing', 'json', json);
          if (selector == undefined)
              return _json.exit('set', 'missing', 'selector', selector);
          if (!_w.isString(selector))
              return _json.exit('set', 'noString', 'selector', selector);
          return (value === undefined || value === null || (typeof value === 'number' && isNaN(value)))
              ? _json.remove(json, selector) : deepJSON(json, selector, value);
          // return deepJSON(json, selector, value); // Now removes the property if the value is empty. Maybe should keep it instead?
      };
  
      _json.remove = function(json, selector) {
          if (json == undefined) return _json.exit('remove', 'missing', 'json', json);
          if (selector == undefined)
              return _json.exit('remove', 'missing', 'selector', selector);
          if (!_w.isString(selector))
              return _json.exit('remove', 'noString', 'selector', selector);
          return deepJSON(json, selector, null, true);
      };
  
      _json.push = function(json, selector, value, force) {
          if (json == undefined) return _json.exit('push', 'missing', 'json', json);
          if (selector == undefined)
              return _json.exit('push', 'missing', 'selector', selector);
          var array = _json.get(json, selector);
          if (!_w.isArray(array)) {
              if (force) {
                  array = [];
              } else {
                  return _json.exit('push', 'noArray', 'array', array);
              }
          }
          array.push(value);
          return _json.set(json, selector, array);
      };
  
      _json.unshift = function(json, selector, value) {
          if (json == undefined)
              return _json.exit('unshift', 'missing', 'json', json);
          if (selector == undefined)
              return _json.exit('unshift', 'missing', 'selector', selector);
          if (value == undefined)
              return _json.exit('unshift', 'missing', 'value', value);
          var array = _json.get(json, selector);
          if (!_w.isArray(array))
              return _json.exit('unshift', 'noArray', 'array', array);
          array.unshift(value);
          return _json.set(json, selector, array);
      };
  
      _json.flatten = function(json) {
          if (json.constructor.name != 'Object')
              return _json.exit('flatten', 'noJSON', 'json', json);
  
          var result = {};
          function recurse(cur, prop) {
              if (Object(cur) !== cur) {
                  result[prop] = cur;
              } else if (Array.isArray(cur)) {
                  for (var i = 0, l = cur.length; i < l; i++) {
                      recurse(cur[i], prop ? prop + '.' + i : '' + i);
                      if (l == 0) result[prop] = [];
                  }
              } else {
                  var isEmpty = true;
                  for (var p in cur) {
                      isEmpty = false;
                      recurse(cur[p], prop ? prop + '.' + p : p);
                  }
                  if (isEmpty) result[prop] = {};
              }
          }
          recurse(json, '');
          return result;
      };
  
      _json.unflatten = function(data) {
          if (Object(data) !== data || Array.isArray(data)) return data;
          var result = {}, cur, prop, idx, last, temp;
          for (var p in data) {
              (cur = result), (prop = ''), (last = 0);
              do {
                  idx = p.indexOf('.', last);
                  temp = p.substring(last, idx !== -1 ? idx : undefined);
                  cur = cur[prop] || (cur[prop] = !isNaN(parseInt(temp)) ? [] : {});
                  prop = temp;
                  last = idx + 1;
              } while (idx >= 0);
              cur[prop] = data[p];
          }
          return result[''];
      };
  
      _json.prettyprint = function(json) {
          return JSON.stringify(json, undefined, 2);
      };
  
      //////////////////////////////////////////
      // wQuery (mini replacement for jQuery) //
      //////////////////////////////////////////
  
      var wQuery = function() {};
      wQuery.constructor = wQuery;
  
      wQuery.prototype.dom = function(selector, createOptions) {
          var self = this, elements = [];
  
          if (createOptions) {
              var element = document.createElement(selector);
              for (var k in createOptions) {
                  element[k] = createOptions[k];
              }
          } else {
              if (_w.isString(selector)) {
                  elements = [].slice.call(document.querySelectorAll(selector));
              } else {
                  if (_w.isObject(selector) && selector.attributes) {
                      elements = [selector];
                  }
              }
              self._elements = elements;
              self.length = elements.length;
              return self;
          }
      };
  
      wQuery.prototype.on = function(events, fn) {
          var self = this, elements = self._elements;
          events = events.split(' ');
          for (var i = 0, lenEl = elements.length; i < lenEl; i++) {
              var element = elements[i];
              for (var j = 0, lenEv = events.length; j < lenEv; j++) {
                  if (element.addEventListener) {
                      element.addEventListener(events[j], fn, false);
                  }
              }
          }
      };
  
      wQuery.prototype.find = function(selector) {
          var self = this, element = self.get(0), elements = [];
  
          if (_w.isString(selector)) {
              elements = [].slice.call(element.querySelectorAll(selector));
          }
          self._elements = elements;
          return self;
      };
  
      wQuery.prototype.get = function(index, chain) {
          var self = this,
              elements = self._elements || [],
              element = elements[index] || {};
  
          if (chain) {
              self._element = element;
              return self;
          } else {
              return _w.isNumber(index) ? element : elements;
          }
      };
  
      wQuery.prototype.reverse = function() {
          this._elements = this._elements.reverse();
          return this;
      };
  
      wQuery.prototype.val = function(value) {
          return this.prop('value', value);
      };
  
      wQuery.prototype.type = function(value) {
          return this.prop('type', value);
      };
  
      wQuery.prototype.html = function(value) {
          return this.prop('innerHTML', value);
      };
  
      wQuery.prototype.text = function(value) {
          return this.prop('innerHTML', _escapeHTML(value));
      };
  
      wQuery.prototype.prop = function(prop, value) {
          var self = this, elements = self._elements;
  
          for (var i in elements) {
              if (_w.isUndefined(value)) {
                  return elements[i][prop];
              } else {
                  elements[i][prop] = value;
              }
          }
      };
  
      wQuery.prototype.attr = function(attr, value) {
          var self = this, elements = self._elements;
          for (var i in elements) {
              if (value === undefined) {
                  return elements[i].getAttribute(attr);
              } else {
                  if (typeof value == 'object') {
                      elements[i].setAttribute(attr, JSON.stringify(value));
                  } else {
                      elements[i].setAttribute(attr, value);
                  }
              }
          }
          return self;
      };
  
      wQuery.prototype.removeAttr = function(attr) {
          var self = this;
          for (var i in self._elements)
              self._elements[i].removeAttribute(attr);
          return self;
      };
  
      wQuery.prototype.addClass = function(c) {
          var self = this;
          for (var i in self._elements)
              self._elements[i].classList.add(c);
          return self;
      };
  
      wQuery.prototype.removeClass = function(c) {
          var self = this;
          for (var i in self._elements)
              self._elements[i].classList.remove(c);
          return self;
      };
  
      wQuery.prototype.parents = function(selector) {
          var self = this,
              element = self.get(0),
              parent = element.parentNode,
              parents = [];
  
          while (parent !== null) {
              var o = parent,
                  matches = matchesSelector(o, selector),
                  isNotDomRoot = o.doctype === undefined ? true : false;
              if (!selector) {
                  matches = true;
              }
              if (matches && isNotDomRoot) {
                  parents.push(o);
              }
              parent = o.parentNode;
          }
          self._elements = parents;
          return self;
      };
  
      wQuery.prototype.parent = function(selector) {
          var self = this,
              element = self.get(0),
              o = element.parentNode,
              matches = matchesSelector(o, selector);
          if (!selector) {
              matches = true;
          }
          return matches ? o : {};
      };
  
      wQuery.prototype.clone = function(chain) {
          var self = this, element = self.get(0), clone = element.cloneNode(true);
          self._elements = [clone];
          return chain ? self : clone;
      };
  
      wQuery.prototype.empty = function(chain) {
          var self = this, element = self.get(0);
          if (!element || !element.hasChildNodes) {
              return chain ? self : element;
          }
  
          while (element.hasChildNodes()) {
              element.removeChild(element.lastChild);
          }
          return chain ? self : element;
      };
  
      wQuery.prototype.replaceWith = function(newDOM) {
          var self = this, oldDOM = self.get(0), parent = oldDOM.parentNode;
          parent.replaceChild(newDOM, oldDOM);
      };
  
      wQuery.prototype.ready = function(callback) {
          if (document && _w.isFunction(document.addEventListener)) {
              document.addEventListener('DOMContentLoaded', callback, false);
          } else if (window && _w.isFunction(window.addEventListener)) {
              window.addEventListener('load', callback, false);
          } else {
              document.onreadystatechange = function() {
                  if (document.readyState === 'complete') {
                      callback();
                  }
              };
          }
      };
  
      //////////////////////
      // WATCH DOM EVENTS //
      //////////////////////
  
      way = new WAY();
  
      var timeoutInput = null;
      var eventInputChange = function(e) {
          if (timeoutInput) {
              clearTimeout(timeoutInput);
          }
          timeoutInput = setTimeout(function() {
              var element = w.dom(e.target).get(0);
              way.dom(element).toStorage();
          }, way.options.timeoutInput);
      };
  
      var eventClear = function(e) {
          e.preventDefault();
          var options = way.dom(this).getOptions();
          way.remove(options.data, options);
      };
  
      var eventPush = function(e) {
          e.preventDefault();
          var options = way.dom(this).getOptions();
          if (!options || !options['action-push']) {
              return false;
          }
          var split = options['action-push'].split(':'),
              selector = split[0] || null,
              value = split[1] || null;
          way.push(selector, value, options);
      };
  
      var eventRemove = function(e) {
          e.preventDefault();
          var options = way.dom(this).getOptions();
          if (!options || !options['action-remove']) {
              return false;
          }
          way.remove(options['action-remove'], options);
      };
  
      var timeoutDOM = null;
      var eventDOMChange = function() {
          // We need to register dynamically added bindings so we do it by watching DOM changes
          // We use a timeout since "DOMSubtreeModified" gets triggered on every change in the DOM (even input value changes)
          // so we can limit the number of scans when a user is typing something
          if (timeoutDOM) {
              clearTimeout(timeoutDOM);
          }
          timeoutDOM = setTimeout(function() {
              way.registerDependencies();
              setEventListeners();
          }, way.options.timeoutDOM);
      };
  
      //////////////
      // INITIATE //
      //////////////
  
      w = new wQuery();
      way.w = w;
  
      var setEventListeners = function() {
          w.dom('body').on('DOMSubtreeModified', eventDOMChange);
          w.dom('[' + tagPrefix + '-data]').on('input change', eventInputChange);
          w.dom('[' + tagPrefix + '-clear]').on('click', eventClear);
          w.dom('[' + tagPrefix + '-action-remove]').on('click', eventRemove);
          w.dom('[' + tagPrefix + '-action-push]').on('click', eventPush);
      };
  
      var eventInit = function() {
          setEventListeners();
          way.restore();
          way.setDefaults();
          way.registerDependencies();
          way.updateDependencies();
      };
  
      w.ready(eventInit);
  
      return way;
  });
  