text: (function(){
  /* BUNDLE START 003 */
  "use strict";
  
  
  var MetaphorJs = {
  
  
  };
  
  
  var toString = Object.prototype.toString;
  
  var undf = undefined;
  
  
  
  
  var varType = function(){
  
      var types = {
          '[object String]': 0,
          '[object Number]': 1,
          '[object Boolean]': 2,
          '[object Object]': 3,
          '[object Function]': 4,
          '[object Array]': 5,
          '[object RegExp]': 9,
          '[object Date]': 10
      };
  
  
      /*
       * 'string': 0,
       * 'number': 1,
       * 'boolean': 2,
       * 'object': 3,
       * 'function': 4,
       * 'array': 5,
       * 'null': 6,
       * 'undefined': 7,
       * 'NaN': 8,
       * 'regexp': 9,
       * 'date': 10,
       * unknown: -1
       * @param {*} value
       * @returns {number}
       */
  
  
  
      return function varType(val) {
  
          if (!val) {
              if (val === null) {
                  return 6;
              }
              if (val === undf) {
                  return 7;
              }
          }
  
          var num = types[toString.call(val)];
  
          if (num === undf) {
              return -1;
          }
  
          if (num === 1 && isNaN(val)) {
              return 8;
          }
  
          return num;
      };
  
  }();
  
  
  
  function isString(value) {
      return typeof value == "string" || value === ""+value;
      //return typeof value == "string" || varType(value) === 0;
  };
  
  
  
  /**
   * @function trim
   * @param {String} value
   * @returns {string}
   */
  var trim = function() {
      // native trim is way faster: http://jsperf.com/angular-trim-test
      // but IE doesn't have it... :-(
      if (!String.prototype.trim) {
          return function(value) {
              return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value;
          };
      }
      return function(value) {
          return isString(value) ? value.trim() : value;
      };
  }();
  
  
  var nextUid = function(){
      var uid = ['0', '0', '0'];
  
      // from AngularJs
      /**
       * @returns {String}
       */
      return function nextUid() {
          var index = uid.length;
          var digit;
  
          while(index) {
              index--;
              digit = uid[index].charCodeAt(0);
              if (digit == 57 /*'9'*/) {
                  uid[index] = 'A';
                  return uid.join('');
              }
              if (digit == 90  /*'Z'*/) {
                  uid[index] = '0';
              } else {
                  uid[index] = String.fromCharCode(digit + 1);
                  return uid.join('');
              }
          }
          uid.unshift('0');
          return uid.join('');
      };
  }();
  
  
  
  
  /**
   * @param {*} value
   * @returns {boolean}
   */
  function isArray(value) {
      return typeof value === "object" && varType(value) === 5;
  };
  
  
  
  /**
   * @param {*} list
   * @returns {[]}
   */
  function toArray(list) {
      if (list && !list.length != undf && list !== ""+list) {
          for(var a = [], i =- 1, l = list.length>>>0; ++i !== l; a[i] = list[i]){}
          return a;
      }
      else if (list) {
          return [list];
      }
      else {
          return [];
      }
  };
  
  function isFunction(value) {
      return typeof value == 'function';
  };
  
  /**
   * @param {string} str
   * @param {string} separator
   * @param {bool} allowEmpty
   * @returns {[]}
   */
  var split = function(str, separator, allowEmpty) {
  
      var l       = str.length,
          sl      = separator.length,
          i       = 0,
          prev    = 0,
          prevChar= "",
          inQDbl  = false,
          inQSng  = false,
          parts   = [],
          esc     = "\\",
          char;
  
      if (!sl) {
          return [str];
      }
  
      for (; i < l; i++) {
  
          char = str.charAt(i);
  
          if (char == esc) {
              i++;
              continue;
          }
  
          if (char == '"') {
              inQDbl = !inQDbl;
              continue;
          }
          if (char == "'") {
              inQSng = !inQSng;
              continue;
          }
  
          if (!inQDbl && !inQSng) {
              if ((sl == 1 && char == separator) ||
                  (sl > 1 && str.substring(i, i + sl) == separator)) {
  
                  if (str.substr(i - 1, sl) == separator ||
                      str.substr(i + 1, sl) == separator) {
  
                      if (!allowEmpty) {
                          i += (sl - 1);
                          continue;
                      }
                  }
  
                  parts.push(str.substring(prev, i).replace(esc + separator, separator));
                  prev = i + sl;
                  i += (sl - 1);
              }
          }
  
          prevChar = char;
      }
  
      parts.push(str.substring(prev).replace(esc + separator, separator));
  
      return parts;
  };
  
  
  
  function isDate(value) {
      return varType(value) === 10;
  };
  
  
  
  function isRegExp(value) {
      return varType(value) === 9;
  };
  
  function isWindow(obj) {
      return obj === window ||
             (obj && obj.document && obj.location && obj.alert && obj.setInterval);
  };
  
  
  
  // from Angular
  
  var equals = function(){
  
      var equals = function equals(o1, o2) {
          if (o1 === o2) return true;
          if (o1 === null || o2 === null) return false;
          if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
          var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
          if (t1 == t2) {
              if (t1 == 'object') {
                  if (isArray(o1)) {
                      if (!isArray(o2)) return false;
                      if ((length = o1.length) == o2.length) {
                          for(key=0; key<length; key++) {
                              if (!equals(o1[key], o2[key])) return false;
                          }
                          return true;
                      }
                  } else if (isDate(o1)) {
                      return isDate(o2) && o1.getTime() == o2.getTime();
                  } else if (isRegExp(o1) && isRegExp(o2)) {
                      return o1.toString() == o2.toString();
                  } else {
                      if (isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
                      keySet = {};
                      for(key in o1) {
                          if (key.charAt(0) == '$' || isFunction(o1[key])) {//&& typeof o1[key] == "object") {
                              continue;
                          }
                          //if (isFunction(o1[key])) {
                          //    continue;
                          //}
                          if (!equals(o1[key], o2[key])) {
                              return false;
                          }
                          keySet[key] = true;
                      }
                      for(key in o2) {
                          if (!keySet.hasOwnProperty(key) &&
                              key.charAt(0) != '$' &&
                              o2[key] !== undf &&
                              !isFunction(o2[key])) return false;
                      }
                      return true;
                  }
              }
          }
          return false;
      };
  
      return equals;
  }();
  
  
  
  function isPlainObject(value) {
      // IE < 9 returns [object Object] from toString(htmlElement)
      return typeof value == "object" &&
             varType(value) === 3 &&
              !value.nodeType &&
              value.constructor === Object;
  
  };
  
  var strUndef = "undefined";
  
  
  
  var copy = function() {
  
      var win = typeof window != strUndef ? window : null,
          glob = typeof global != strUndef ? global : null;
  
      var copy = function copy(source, dest){
  
          if (win && source === win) {
              throw new Error("Cannot copy window object");
          }
          if (glob && source === glob) {
              throw new Error("Cannot copy global object");
          }
  
          if (!dest) {
              dest = source;
              if (source) {
                  if (isArray(source)) {
                      dest = copy(source, []);
                  } else if (isDate(source)) {
                      dest = new Date(source.getTime());
                  } else if (isRegExp(source)) {
                      dest = new RegExp(source.source);
                  } else if (isPlainObject(source)) {
                      dest = copy(source, {});
                  }
              }
          } else {
              if (source === dest) {
                  throw new Error("Objects are identical");
              }
              if (isArray(source)) {
                  dest.length = 0;
                  for ( var i = 0, l = source.length; i < l; i++) {
                      dest.push(copy(source[i]));
                  }
              } else {
                  var key;
                  for (key in dest) {
                      delete dest[key];
                  }
                  for (key in source) {
                      if (source.hasOwnProperty(key)) {
                          if (key.charAt(0) == '$' || isFunction(source[key])) {
                              dest[key] = source[key];
                          }
                          else {
                              dest[key] = copy(source[key]);
                          }
                      }
                  }
              }
          }
          return dest;
      };
  
      return copy;
  }();
  
  
  var slice = Array.prototype.slice;
  
  function isBool(value) {
      return value === true || value === false;
  };
  
  
  
  
  var extend = function(){
  
      /**
       * @param {Object} dst
       * @param {Object} src
       * @param {Object} src2 ... srcN
       * @param {boolean} override = false
       * @param {boolean} deep = false
       * @returns {object}
       */
      var extend = function extend() {
  
  
          var override    = false,
              deep        = false,
              args        = slice.call(arguments),
              dst         = args.shift(),
              src,
              k,
              value;
  
          if (isBool(args[args.length - 1])) {
              override    = args.pop();
          }
          if (isBool(args[args.length - 1])) {
              deep        = override;
              override    = args.pop();
          }
  
          while (args.length) {
              // IE < 9 fix: check for hasOwnProperty presence
              if ((src = args.shift()) && src.hasOwnProperty) {
                  for (k in src) {
  
                      if (src.hasOwnProperty(k) && (value = src[k]) !== undf) {
  
                          if (deep) {
                              if (dst[k] && isPlainObject(dst[k]) && isPlainObject(value)) {
                                  extend(dst[k], value, override, deep);
                              }
                              else {
                                  if (override === true || dst[k] == undf) { // == checks for null and undefined
                                      if (isPlainObject(value)) {
                                          dst[k] = {};
                                          extend(dst[k], value, override, true);
                                      }
                                      else {
                                          dst[k] = value;
                                      }
                                  }
                              }
                          }
                          else {
                              if (override === true || dst[k] == undf) {
                                  dst[k] = value;
                              }
                          }
                      }
                  }
              }
          }
  
          return dst;
      };
  
      return extend;
  }();
  
  
  
  function isPrimitive(value) {
      var vt = varType(value);
      return vt < 3 && vt > -1;
  };
  
  function returnFalse() {
      return false;
  };
  
  /**
   * @param {Function} fn
   * @param {*} context
   */
  var bind = Function.prototype.bind ?
                function(fn, context){
                    return fn.bind(context);
                } :
                function(fn, context) {
                    return function() {
                        return fn.apply(context, arguments);
                    };
                };
  
  
  /**
   * @param {Function} fn
   * @param {Object} context
   * @param {[]} args
   * @param {number} timeout
   */
  function async(fn, context, args, timeout) {
      return setTimeout(function(){
          fn.apply(context, args || []);
      }, timeout || 0);
  };
  
  
  
  
  /**
   * This class is private - you can't create an event other than via Observable.
   * See {@link class:Observable} reference.
   * @class ObservableEvent
   * @private
   */
  var ObservableEvent = function(name, options) {
  
      var self    = this;
  
      self.name           = name;
      self.listeners      = [];
      self.map            = {};
      self.hash           = nextUid();
      self.uni            = '$$' + name + '_' + self.hash;
      self.suspended      = false;
      self.lid            = 0;
  
      if (typeof options === "object" && options !== null) {
          extend(self, options, true, false);
      }
      else {
          self.returnResult = options;
      }
  };
  
  
  extend(ObservableEvent.prototype, {
  
      name: null,
      listeners: null,
      map: null,
      hash: null,
      uni: null,
      suspended: false,
      lid: null,
      returnResult: null,
      autoTrigger: null,
      lastTrigger: null,
      triggerFilter: null,
      filterContext: null,
      expectPromises: false,
      resolvePromises: false,
  
      /**
       * Get event name
       * @method
       * @returns {string}
       */
      getName: function() {
          return this.name;
      },
  
      /**
       * @method
       */
      destroy: function() {
          var self        = this,
              k;
  
          for (k in self) {
              self[k] = null;
          }
      },
  
      /**
       * @method
       * @param {function} fn Callback function { @required }
       * @param {object} context Function's "this" object
       * @param {object} options See {@link class:Observable.on}
       */
      on: function(fn, context, options) {
  
          if (!fn) {
              return null;
          }
  
          context     = context || null;
          options     = options || {};
  
          var self        = this,
              uni         = self.uni,
              uniContext  = fn || context;
  
          if (uniContext[uni] && !options.allowDupes) {
              return null;
          }
  
          var id      = ++self.lid,
              first   = options.first || false;
  
          uniContext[uni]  = id;
  
          var e = {
              fn:         fn,
              context:    context,
              uniContext: uniContext,
              id:         id,
              async:      false,
              called:     0, // how many times the function was triggered
              limit:      0, // how many times the function is allowed to trigger
              start:      1, // from which attempt it is allowed to trigger the function
              count:      0, // how many attempts to trigger the function was made
              append:     null, // append parameters
              prepend:    null // prepend parameters
          };
  
          extend(e, options, true, false);
  
          if (e.async === true) {
              e.async = 1;
          }
  
          if (first) {
              self.listeners.unshift(e);
          }
          else {
              self.listeners.push(e);
          }
  
          self.map[id] = e;
  
          if (self.autoTrigger && self.lastTrigger && !self.suspended) {
              var prevFilter = self.triggerFilter;
              self.triggerFilter = function(l){
                  if (l.id === id) {
                      return prevFilter ? prevFilter(l) !== false : true;
                  }
                  return false;
              };
              self.trigger.apply(self, self.lastTrigger);
              self.triggerFilter = prevFilter;
          }
  
          return id;
      },
  
      /**
       * @method
       * @param {function} fn Callback function { @required }
       * @param {object} context Function's "this" object
       * @param {object} options See {@link class:Observable.on}
       */
      once: function(fn, context, options) {
  
          options = options || {};
          options.limit = 1;
  
          return this.on(fn, context, options);
      },
  
      /**
       * @method
       * @param {function} fn Callback function { @required }
       * @param {object} context Callback context
       */
      un: function(fn, context) {
  
          var self        = this,
              inx         = -1,
              uni         = self.uni,
              listeners   = self.listeners,
              id;
  
          if (fn == parseInt(fn)) {
              id      = parseInt(fn);
          }
          else {
              context = context || fn;
              id      = context[uni];
          }
  
          if (!id) {
              return false;
          }
  
          for (var i = 0, len = listeners.length; i < len; i++) {
              if (listeners[i].id === id) {
                  inx = i;
                  delete listeners[i].uniContext[uni];
                  break;
              }
          }
  
          if (inx === -1) {
              return false;
          }
  
          listeners.splice(inx, 1);
          delete self.map[id];
          return true;
      },
  
      /**
       * @method hasListener
       * @return bool
       */
  
      /**
       * @method
       * @param {function} fn Callback function { @required }
       * @param {object} context Callback context
       * @return boolean
       */
      hasListener: function(fn, context) {
  
          var self    = this,
              listeners   = self.listeners,
              id;
  
          if (fn) {
  
              context = context || fn;
  
              if (!isFunction(fn)) {
                  id  = parseInt(fn);
              }
              else {
                  id  = context[self.uni];
              }
  
              if (!id) {
                  return false;
              }
  
              for (var i = 0, len = listeners.length; i < len; i++) {
                  if (listeners[i].id === id) {
                      return true;
                  }
              }
  
              return false;
          }
          else {
              return listeners.length > 0;
          }
      },
  
  
      /**
       * @method
       */
      removeAllListeners: function() {
          var self    = this,
              listeners = self.listeners,
              uni     = self.uni,
              i, len;
  
          for (i = 0, len = listeners.length; i < len; i++) {
              delete listeners[i].uniContext[uni];
          }
          self.listeners   = [];
          self.map         = {};
      },
  
      /**
       * @method
       */
      suspend: function() {
          this.suspended = true;
      },
  
      /**
       * @method
       */
      resume: function() {
          this.suspended = false;
      },
  
  
      _prepareArgs: function(l, triggerArgs) {
          var args;
  
          if (l.append || l.prepend) {
              args    = slice.call(triggerArgs);
              if (l.prepend) {
                  args    = l.prepend.concat(args);
              }
              if (l.append) {
                  args    = args.concat(l.append);
              }
          }
          else {
              args = triggerArgs;
          }
  
          return args;
      },
  
      /**
       * @method
       * @return {*}
       */
      trigger: function() {
  
          var self            = this,
              listeners       = self.listeners,
              rr              = self.returnResult,
              filter          = self.triggerFilter,
              filterContext   = self.filterContext,
              expectPromises  = self.expectPromises,
              results         = [],
              prevPromise,
              resPromise,
              args, 
              resolver;
  
          if (self.suspended) {
              return null;
          }
  
          if (self.autoTrigger) {
              self.lastTrigger = slice.call(arguments);
          }
  
          if (listeners.length === 0) {
              return null;
          }
  
          var ret     = rr === "all" || rr === "concat" ?
                          [] : 
                          (rr === "merge" ? {} : null),
              q, l,
              res;
  
          if (rr === "first") {
              q = [listeners[0]];
          }
          else {
              // create a snapshot of listeners list
              q = slice.call(listeners);
          }
  
          // now if during triggering someone unsubscribes
          // we won't skip any listener due to shifted
          // index
          while (l = q.shift()) {
  
              // listener may already have unsubscribed
              if (!l || !self.map[l.id]) {
                  continue;
              }
  
              args = self._prepareArgs(l, arguments);
  
              if (filter && filter.call(filterContext, l, args, self) === false) {
                  continue;
              }
  
              if (l.filter && l.filter.apply(l.filterContext || l.context, args) === false) {
                  continue;
              }
  
              l.count++;
  
              if (l.count < l.start) {
                  continue;
              }
  
              if (l.async && !expectPromises) {
                  res = null;
                  async(l.fn, l.context, args, l.async);
              }
              else {
                  if (expectPromises) {
                      resolver = function(l, rr, args){
                          return function(value) {
  
                              if (rr === "pipe") {
                                  args[0] = value;
                                  args = self._prepareArgs(l, args);
                              }
                              
                              return l.fn.apply(l.context, args);
                          }
                      }(l, rr, slice.call(arguments));
  
                      if (prevPromise) {
                          res = prevPromise.then(resolver);
                      }
                      else {
                          res = l.fn.apply(l.context, args);
                      }
  
                      res.catch(function(err){
                          console.log(err);
                      });
                  }
                  else {
                      res = l.fn.apply(l.context, args);
                  }
              }
  
              l.called++;
  
              if (l.called === l.limit) {
                  self.un(l.id);
              }
  
              // This rule is valid in all cases sync and async.
              // It either returns first value or first promise.
              if (rr === "first") {
                  return res;
              }
          
              // Promise branch
              if (expectPromises) {
              
                  // we collect all results for further processing/resolving
                  results.push(res);
  
                  if (rr === "pipe" && res) {
                      prevPromise = res;
                  }
              }
              else {
                  if (rr !== null) {
                      if (rr === "all") {
                          ret.push(res);
                      }
                      else if (rr === "concat" && res) {
                          ret = ret.concat(res);
                      }
                      else if (rr === "merge") {
                          extend(ret, res, true, false);
                      }
                      else if (rr === "nonempty" && res) {
                          return res;
                      }
                      else if (rr === "pipe") {
                          ret = res;
                          arguments[0] = res;
                      }
                      else if (rr === "last") {
                          ret = res;
                      }
                      else if (rr === false && res === false) {
                          return false;
                      }
                      else if (rr === true && res === true) {
                          return true;
                      }
                  }
              }
          }
  
          if (expectPromises) {
              resPromise = Promise.all(results);
              if (self.resolvePromises && rr !== null && rr !== "all") {
                  resPromise = resPromise.then(function(values){
                      var i, l = values.length, res;
                      for(i = 0; i < l; i++) {
                          res = values[i];
                          if (rr === "concat" && res) {
                              ret = ret.concat(res);
                          }
                          else if (rr === "merge") {
                              extend(ret, res, true, false);
                          }
                          else if (rr === "nonempty" && res) {
                              return res;
                          }
                          else if (rr === false && res === false) {
                              return false;
                          }
                          else if (rr === true && res === true) {
                              return true;
                          }
                      }
                      return ret;
                  });
              }
              return resPromise;
          }
          else return ret;
      }
  }, true, false);
  
  
  
  
  
  
  
  
  /**
   * @description A javascript event system implementing two patterns - observable and collector.
   * @description Observable:
   * @code examples/observable.js
   *
   * @description Collector:
   * @code examples/collector.js
   *
   * @class Observable
   * @version 1.2
   * @author Ivan Kuindzhi
   * @link https://github.com/kuindji/metaphorjs-observable
   */
  var Observable = function() {
  
      this.events = {};
  
  };
  
  
  extend(Observable.prototype, {
  
  
  
      /**
      * You don't have to call this function unless you want to pass params other than event name.
      * Normally, events are created automatically.
      *
      * @method createEvent
      * @access public
      * @param {string} name {
      *       Event name
      *       @required
      * }
      * @param {bool|string} returnResult {
      *   false -- return first 'false' result and stop calling listeners after that<br>
      *   true -- return first 'true' result and stop calling listeners after that<br>
      *   "all" -- return all results as array<br>
      *   "concat" -- merge all results into one array (each result must be array)<br>
      *   "merge" -- merge all results into one object (each result much be object)<br>
      *   "pipe" -- pass return value of previous listener to the next listener.
      *             Only first trigger parameter is being replaced with return value,
      *             others stay as is.<br>
      *   "first" -- return result of the first handler (next listener will not be called)<br>
      *   "nonempty" -- return first nonempty result<br>
      *   "last" -- return result of the last handler (all listeners will be called)<br>
      * }
      * @param {bool} autoTrigger {
      *   once triggered, all future subscribers will be automatically called
      *   with last trigger params
      *   @code examples/autoTrigger.js
      * }
      * @param {function} triggerFilter {
      *   This function will be called each time event is triggered. Return false to skip listener.
      *   @code examples/triggerFilter.js
      *   @param {object} listener This object contains all information about the listener, including
      *       all data you provided in options while subscribing to the event.
      *   @param {[]} arguments
      *   @return {bool}
      * }
      * @param {object} filterContext triggerFilter's context
      * @return {ObservableEvent}
      */
  
      /**
       * @method createEvent
       * @param {string} name
       * @param {object} options {
       *  Options object or returnResult value. All options are optional.
       *  @type {string|bool} returnResult
       *  @type {bool} autoTrigger
       *  @type {function} triggerFilter
       *  @type {object} filterContext
       *  @type {bool} expectPromises
       *  @type {bool} resolvePromises
       * }
       * @returns {ObservableEvent}
       */
      createEvent: function(name, options) {
          name = name.toLowerCase();
          var events  = this.events;
          if (!events[name]) {
              events[name] = new ObservableEvent(name, options);
          }
          return events[name];
      },
  
      /**
      * @method
      * @access public
      * @param {string} name Event name
      * @return {ObservableEvent|undefined}
      */
      getEvent: function(name) {
          name = name.toLowerCase();
          return this.events[name];
      },
  
      /**
      * Subscribe to an event or register collector function.
      * @method
      * @access public
      * @param {string} name {
      *       Event name. Use '*' to subscribe to all events.
      *       @required
      * }
      * @param {function} fn {
      *       Callback function
      *       @required
      * }
      * @param {object} context "this" object for the callback function
      * @param {object} options {
      *       You can pass any key-value pairs in this object. All of them will be passed 
      *       to triggerFilter (if you're using one).
      *       @type {bool} first {
      *           True to prepend to the list of handlers
      *           @default false
      *       }
      *       @type {number} limit {
      *           Call handler this number of times; 0 for unlimited
      *           @default 0
      *       }
      *       @type {number} start {
      *           Start calling handler after this number of calls. Starts from 1
      *           @default 1
      *       }
          *      @type {[]} append Append parameters
          *      @type {[]} prepend Prepend parameters
          *      @type {bool} allowDupes allow the same handler twice
          *      @type {bool|int} async run event asynchronously. If event was
          *                      created with <code>expectPromises: true</code>, 
          *                      this option is ignored.
      * }
      */
      on: function(name, fn, context, options) {
          name = name.toLowerCase();
          var events  = this.events;
          if (!events[name]) {
              events[name] = new ObservableEvent(name);
          }
          return events[name].on(fn, context, options);
      },
  
      /**
      * Same as {@link class:Observable.on}, but options.limit is forcefully set to 1.
      * @method
      * @access public
      */
      once: function(name, fn, context, options) {
          options     = options || {};
          options.limit = 1;
          return this.on(name, fn, context, options);
      },
  
      /**
      * Unsubscribe from an event
      * @method
      * @access public
      * @param {string} name Event name
      * @param {function} fn Event handler
      * @param {object} context If you called on() with context you must 
      *                         call un() with the same context
      */
      un: function(name, fn, context) {
          name = name.toLowerCase();
          var events  = this.events;
          if (!events[name]) {
              return;
          }
          events[name].un(fn, context);
      },
  
      /**
       * Relay all events of <code>eventSource</code> through this observable.
       * @method
       * @access public
       * @param {object} eventSource
       * @param {string} eventName
       */
      relayEvent: function(eventSource, eventName) {
          eventSource.on(eventName, this.trigger, this, {
              prepend: eventName === "*" ? null : [eventName]
          });
      },
  
      /**
       * Stop relaying events of <code>eventSource</code>
       * @method
       * @access public
       * @param {object} eventSource
       * @param {string} eventName
       */
      unrelayEvent: function(eventSource, eventName) {
          eventSource.un(eventName, this.trigger, this);
      },
  
      /**
       * @method hasListener
       * @access public
       * @return bool
       */
  
      /**
      * @method hasListener
      * @access public
      * @param {string} name Event name { @required }
      * @return bool
      */
  
      /**
      * @method
      * @access public
      * @param {string} name Event name { @required }
      * @param {function} fn Callback function { @required }
      * @param {object} context Function's "this" object
      * @return bool
      */
      hasListener: function(name, fn, context) {
          var events = this.events;
  
          if (name) {
              name = name.toLowerCase();
              if (!events[name]) {
                  return false;
              }
              return fn ? events[name].hasListener(fn, context) : true;
          }
          else {
              for (name in events) {
                  if (events[name].hasListener()) {
                      return true;
                  }
              }
              return false;
          }
      },
  
      /**
      * @method
      * @access public
      * @param {string} name Event name { @required }
      * @return bool
      */
      hasEvent: function(name) {
          return !!this.events[name];
      },
  
  
      /**
      * Remove all listeners from all events
      * @method removeAllListeners
      * @access public
      */
  
      /**
      * Remove all listeners from specific event
      * @method
      * @access public
      * @param {string} name Event name { @required }
      */
      removeAllListeners: function(name) {
          var events  = this.events;
          if (name) {
              if (!events[name]) {
                  return;
              }
              events[name].removeAllListeners();
          }
          else {
              for (name in events) {
                  events[name].removeAllListeners();
              }
          }
      },
  
      /**
      * Trigger an event -- call all listeners. Also triggers '*' event.
      * @method
      * @access public
      * @param {string} name Event name { @required }
      * @param {*} ... As many other params as needed
      * @return mixed
      */
      trigger: function() {
  
          var name = arguments[0],
              events  = this.events,
              e,
              res = null;
  
          name = name.toLowerCase();
  
          if (events[name]) {
              e = events[name];
              res = e.trigger.apply(e, slice.call(arguments, 1));
          }
          
          // trigger * event with current event name
          // as first argument
          if (e = events["*"]) {
              e.trigger.apply(e, arguments);
          }
          
          return res;
      },
  
      /**
      * Suspend an event. Suspended event will not call any listeners on trigger().
      * @method
      * @access public
      * @param {string} name Event name
      */
      suspendEvent: function(name) {
          name = name.toLowerCase();
          var events  = this.events;
          if (!events[name]) {
              return;
          }
          events[name].suspend();
      },
  
      /**
      * @method
      * @access public
      */
      suspendAllEvents: function() {
          var events  = this.events;
          for (var name in events) {
              events[name].suspend();
          }
      },
  
      /**
      * Resume suspended event.
      * @method
      * @access public
      * @param {string} name Event name
      */
      resumeEvent: function(name) {
          name = name.toLowerCase();
          var events  = this.events;
          if (!events[name]) {
              return;
          }
          events[name].resume();
      },
  
      /**
      * @method
      * @access public
      */
      resumeAllEvents: function() {
          var events  = this.events;
          for (var name in events) {
              events[name].resume();
          }
      },
  
      /**
       * @method
       * @access public
       * @param {string} name Event name
       */
      destroyEvent: function(name) {
          var events  = this.events;
          if (events[name]) {
              events[name].removeAllListeners();
              events[name].destroy();
              delete events[name];
          }
      },
  
  
      /**
      * Destroy observable
      * @method
      * @md-not-inheritable
      * @access public
      */
      destroy: function() {
          var self    = this,
              events  = self.events;
  
          for (var i in events) {
              self.destroyEvent(i);
          }
  
          for (i in self) {
              self[i] = null;
          }
      },
  
      /**
      * Although all methods are public there is getApi() method that allows you
      * extending your own objects without overriding "destroy" (which you probably have)
      * @code examples/api.js
      * @method
      * @md-not-inheritable
      * @returns object
      */
      getApi: function() {
  
          var self    = this;
  
          if (!self.api) {
  
              var methods = [
                      "createEvent", "getEvent", "on", "un", "once", "hasListener", "removeAllListeners",
                      "trigger", "suspendEvent", "suspendAllEvents", "resumeEvent",
                      "resumeAllEvents", "destroyEvent",
                      "relayEvent", "unrelayEvent"
                  ],
                  api = {},
                  name;
  
              for(var i =- 1, l = methods.length;
                      ++i < l;
                      name = methods[i],
                      api[name] = bind(self[name], self)){}
  
              self.api = api;
          }
  
          return self.api;
  
      }
  }, true, false);
  
  
  
  
  
  
  function levenshteinArray(from, to) {
  
      var m = from.length,
          n = to.length,
          D = new Array(m + 1),
          P = new Array(m + 1),
          i, j, c,
          route,
          cost,
          dist,
          ops = 0;
  
      if (m == n && m == 0) {
          return {
              changes: 0,
              distance: 0,
              prescription: []
          };
      }
  
      for (i = 0; i <= m; i++) {
          D[i]    = new Array(n + 1);
          P[i]    = new Array(n + 1);
          D[i][0] = i;
          P[i][0] = 'D';
      }
      for (i = 0; i <= n; i++) {
          D[0][i] = i;
          P[0][i] = 'I';
      }
  
      for (i = 1; i <= m; i++) {
          for (j = 1; j <= n; j++) {
              cost = (!equals(from[i - 1], to[j - 1])) ? 1 : 0;
  
              if(D[i][j - 1] < D[i - 1][j] && D[i][j - 1] < D[i - 1][j - 1] + cost) {
                  //Insert
                  D[i][j] = D[i][j - 1] + 1;
                  P[i][j] = 'I';
              }
              else if(D[i - 1][j] < D[i - 1][j - 1] + cost) {
                  //Delete
                  D[i][j] = D[i - 1][j] + 1;
                  P[i][j] = 'D';
              }
              else {
                  //Replace or noop
                  D[i][j] = D[i - 1][j - 1] + cost;
                  if (cost == 1) {
                      P[i][j] = 'R';
                  }
                  else {
                      P[i][j] = '-';
                  }
              }
          }
      }
  
      //Prescription
      route = [];
      i = m;
      j = n;
  
      do {
          c = P[i][j];
          route.push(c);
          if (c != '-') {
              ops++;
          }
          if(c == 'R' || c == '-') {
              i --;
              j --;
          }
          else if(c == 'D') {
              i --;
          }
          else {
              j --;
          }
      } while((i != 0) || (j != 0));
  
      dist = D[m][n];
  
      return {
          changes: ops / route.length,
          distance: dist,
          prescription: route.reverse()
      };
  };
  
  
  
  var error = (function(){
  
      var listeners = [];
  
      var error = function error(e) {
  
          var i, l;
  
          for (i = 0, l = listeners.length; i < l; i++) {
              if (listeners[i][0].call(listeners[i][1], e) === false) {
                  return;
              }
          }
  
          var stack = (e ? e.stack : null) || (new Error).stack;
  
          if (typeof console != strUndef && console.error) {
              //async(function(){
                  if (e) {
                      console.error(e);
                  }
                  if (stack) {
                      console.error(stack);
                  }
              //});
          }
          else {
              throw e;
          }
      };
  
      error.on = function(fn, context) {
          error.un(fn, context);
          listeners.push([fn, context]);
      };
  
      error.un = function(fn, context) {
          var i, l;
          for (i = 0, l = listeners.length; i < l; i++) {
              if (listeners[i][0] === fn && listeners[i][1] === context) {
                  listeners.splice(i, 1);
                  break;
              }
          }
      };
  
      return error;
  }());
  
  
  
  
  function emptyFn(){};
  
  
  
  var functionFactory = function() {
  
      var REG_REPLACE_EXPR    = /((^|[^a-z0-9_$\]\)'"])|(this))(\.)([^0-9])/ig,
          REG_REPLACER        = "$2____.$5",
  
          f               = Function,
          fnBodyStart     = 'try {',
          getterBodyEnd   = ';} catch (thrownError) { return undefined; }',
          setterBodyEnd   = ';} catch (thrownError) { return undefined; }',
  
          getterCache     = {},
          getterCacheCnt  = 0,
  
          createGetter    = function createGetter(expr, returnAsCode) {
  
              try {
                  if (!getterCache[expr] || returnAsCode) {
                      getterCacheCnt++;
  
                      var body = "".concat(
                          fnBodyStart,
                          'return ',
                          expr.replace(REG_REPLACE_EXPR, REG_REPLACER),
                          getterBodyEnd
                      );
  
                      if (returnAsCode) {
                          return "function(____) {" + body + "}";
                      }
                      else {
                          return getterCache[expr] = new f(
                              '____',
                              body
                          );
                      }
                  }
                  return getterCache[expr];
              }
              catch (thrownError){
                  error(thrownError);
                  return emptyFn;
              }
          },
  
          setterCache     = {},
          setterCacheCnt  = 0,
  
          createSetter    = function createSetter(expr, returnAsCode) {
              try {
                  if (!setterCache[expr] || returnAsCode) {
                      setterCacheCnt++;
                      var code = expr.replace(REG_REPLACE_EXPR, REG_REPLACER),
                          body = "".concat(fnBodyStart, code, ' = $$$$', setterBodyEnd);
  
                      if (returnAsCode) {
                          return "function(____, $$$$) {" + body + "}";
                      }
                      else {
                          return setterCache[expr] = new f(
                              '____',
                              '$$$$',
                              body
                          );
                      }
                  }
                  return setterCache[expr];
              }
              catch (thrownError) {
                  error(thrownError);
                  return emptyFn;
              }
          },
  
          funcCache       = {},
          funcCacheCnt    = 0,
  
          createFunc      = function createFunc(expr, returnAsCode) {
              try {
                  if (!funcCache[expr] || returnAsCode) {
                      funcCacheCnt++;
  
                      var body = "".concat(
                          fnBodyStart,
                          expr.replace(REG_REPLACE_EXPR, REG_REPLACER),
                          getterBodyEnd
                      );
  
                      if (returnAsCode) {
                          return "function(____) {" + body + "}";
                      }
                      else {
                          return funcCache[expr] = new f(
                              '____',
                              body
                          );
                      }
                  }
                  return funcCache[expr];
              }
              catch (thrownError) {
                  error(thrownError);
                  return emptyFn;
              }
          },
  
          resetCache = function() {
              getterCacheCnt >= 1000 && (getterCache = {});
              setterCacheCnt >= 1000 && (setterCache = {});
              funcCacheCnt >= 1000 && (funcCache = {});
          };
  
      return {
          createGetter: createGetter,
          createSetter: createSetter,
          createFunc: createFunc,
          resetCache: resetCache,
          enableResetCacheInterval: function() {
              setTimeout(resetCache, 10000);
          }
      };
  }();
  
  
  
  var createGetter = functionFactory.createGetter;
  
  
  
  
  var createSetter = functionFactory.createSetter;
  
  
  
  var Watchable = function(){
  
      var isStatic    = function(val) {
  
              if (!isString(val)) {
                  return true;
              }
  
              var first   = val.substr(0, 1),
                  last    = val.length - 1;
  
              if (first === '"' || first === "'") {
                  if (val.indexOf(first, 1) === last) {
                      return val.substring(1, last);
                  }
              }
  
              return false;
          },
  
          prescription2moves = function(a1, a2, prs, getKey) {
  
              var newPrs = [],
                  i, l, k, action,
                  map1 = {},
                  prsi,
                  a2i,
                  index;
  
              for (i = 0, l = a1.length; i < l; i++) {
                  k = getKey(a1[i]);
                  if (k) {
                      map1[k] = i;
                  }
              }
  
              a2i = 0;
              var used = {};
  
              for (prsi = 0, l = prs.length; prsi < l; prsi++) {
  
                  action = prs[prsi];
  
                  if (action === 'D') {
                      continue;
                  }
  
                  k = getKey(a2[a2i]);
  
                  if (k !== undf && used[k] !== true && (index = map1[k]) !== undf) {
                      newPrs.push(index);
                      used[k] = true;
                  }
                  else {
                      newPrs.push(action);
                  }
                  a2i++;
              }
  
              return newPrs;
          },
  
  
          observable;
  
      /**
       * @class Watchable
       */
  
      /**
       * @param {object} dataObj object containing observed property
       * @param {string} code property name or custom code
       * @param {function} fn optional listener
       * @param {object} fnScope optional listener's "this" object
       *  @subparam {*} userData optional data to pass to the listener
       *  @subparam {function} filterLookup
       *  @subparam {*} mock do not calculate real values, use mock instead
       *  @subparam {function} predefined getter fn
       * @param {object} opt
       * @constructor
       */
      var Watchable   = function(dataObj, code, fn, fnScope, opt) {
  
          if (!observable) {
              observable  = new Observable;
          }
  
          opt = opt || {};
  
          var self    = this,
              id      = nextUid(),
              type;
  
          if (opt.filterLookup) {
              self.filterLookup = opt.filterLookup;
          }
  
          self.mock = opt.mock;
          self.origCode = code;
  
          if (opt.mock && code.indexOf(".") === -1) {
              type = "attr";
          }
          else if (code && dataObj) {
              type    = dataObj.hasOwnProperty(code) ? "attr" : "expr";
          }
          else if (code && !dataObj) {
              type = "expr";
          }
  
  
          if (fn) {
              observable.on(id, fn, fnScope || this, {
                  append: [opt.userData],
                  allowDupes: true
              });
          }
  
          if (type === "expr") {
              code        = self._parsePipes(code, dataObj, true);
              code        = self._parsePipes(code, dataObj, false);
  
              if (self.inputPipes || self.pipes) {
                  code    = normalizeExpr(dataObj, code);
                  type    = dataObj.hasOwnProperty(code) ? "attr" : "expr";
              }
  
              if (self.staticValue = isStatic(code)) {
                  type    = "static";
              }
          }
  
          self.userData   = opt.userData;
          self.code       = code;
          self.id         = id;
          self.type       = type;
          self.obj        = dataObj;
  
          if (type === "expr") {
              self.getterFn   = opt.getterFn || createGetter(code);
          }
  
          if (type !== "static" || self.pipes) {
              self.curr = self.curr || self._getValue();
              self.currCopy = isPrimitive(self.curr) ? self.curr : copy(self.curr);
          }
          else {
              self.check = returnFalse;
              self.curr = self.prev = self.staticValue;
          }
      };
  
      extend(Watchable.prototype, {
  
          //namespace: null,
          //nsGet: null,
  
          filterLookup: null,
  
          staticValue: null,
          origCode: null,
          code: null,
          getterFn: null,
          setterFn: null,
          id: null,
          type: null,
          obj: null,
          itv: null,
          curr: null,
          currCopy: null,
          prev: null,
          unfilteredCopy: null,
          unfiltered: null,
          pipes: null,
          inputPipes: null,
          lastSetValue: null,
          userData: null,
          obsrvDelegate: null,
          obsrvChanged: false,
          forcePipes: false,
  
          mock: false,
  
          // means that pipes always return the same output given the same input.
          // if you want to mark pipe as undeterministic - put ? before it
          // {{ .somevalue | ?pipe }}
          // then value will be passed through all pipes on each check.
          deterministic: true,
  
          getConfig: function() {
              var getterFn = null;
              if (this.type === "expr") {
                  getterFn   = createGetter(this.code, true);
              }
              return {
                  type: this.type,
                  code: this.origCode,
                  withoutPipes: this.code,
                  getter: getterFn,
                  hasPipes: this.pipes !== null,
                  hasInputPipes: this.inputPipes !== null
              }
          },
  
          _indexArrayItems: function(a) {
  
              var key = '$$' + this.id,
                  i, l, item;
  
              if (a) {
                  for (i = 0, l = a.length; i < l; i++) {
                      item = a[i];
                      if (item && !isPrimitive(item) && !item[key]) {
                          item[key] = nextUid();
                      }
                  }
              }
          },
  
  
          _parsePipes: function(text, dataObj, input) {
  
              var self        = this,
                  separator   = input ? ">>" : "|",
                  propName    = input ? "inputPipes" : "pipes",
                  cb          = input ? self.onInputParamChange : self.onPipeParamChange;
  
              if (text.indexOf(separator) === -1) {
                  return text;
              }
  
              var parts   = split(text, separator),
                  ret     = input ? parts.pop() : parts.shift(),
                  pipes   = [],
                  pipe,
                  i, l;
  
              for(i = 0, l = parts.length; i < l; i++) {
                  pipe = split(trim(parts[i]), ':');
                  self._addPipe(pipes, pipe, dataObj, cb, false);
              }
  
              if (pipes.length) {
                  self[propName] = pipes;
              }
  
              return trim(ret);
          },
  
          prependInuptPipe: function() {
              this.inputPipes = this.inputPipes || [];
              this._addPipe(
                  this.inputPipes,
                  toArray(arguments),
                  this.obj,
                  this.onInputParamChange,
                  true
              );
          },
          addInuptPipe: function() {
              this.inputPipes = this.inputPipes || [];
              this._addPipe(
                  this.inputPipes,
                  toArray(arguments),
                  this.obj,
                  this.onInputParamChange,
                  false
              );
          },
  
          addPipe: function() {
              this.pipes = this.pipes || [];
              this._addPipe(
                  this.pipes,
                  toArray(arguments),
                  this.obj,
                  this.onPipeParamChange,
                  false
              );
          },
          prependPipe: function() {
              this.pipes = this.pipes || [];
              this._addPipe(
                  this.pipes,
                  toArray(arguments),
                  this.obj,
                  this.onPipeParamChange,
                  true
              );
          },
  
          _addPipe: function(pipes, pipe, dataObj, onParamChange, prepend) {
  
              var self    = this,
                  name    = pipe.shift(),
                  fn      = isFunction(name) ? name : null,
                  ws      = [],
                  fchar   = fn ? null : name.substr(0,1),
                  opt     = {
                      neg: false,
                      dblneg: false,
                      undeterm: false,
                      name: name
                  },
                  i, l;
  
              if (!fn) {
                  if (name.substr(0, 2) === "!!") {
                      name = name.substr(2);
                      opt.dblneg = true;
                  }
                  else {
                      if (fchar === "!") {
                          name = name.substr(1);
                          opt.neg = true;
                      }
                      else if (fchar === "?") {
                          name = name.substr(1);
                          opt.undeterm = true;
                      }
                  }
              }
              else {
                  opt.name = fn.name;
              }
  
              if (self.mock) {
                  fn      = function(){};
              }
              else {
                  if (!fn && self.filterLookup) {
                      fn = self.filterLookup(name);
                  }
                  if (!fn) {
                      fn = (typeof window !== "undefined" ? window[name] : null) || dataObj[name];
                  }
              }
  
              //console.log(!!self.nsGet, name, fn)
  
              if (isFunction(fn)) {
  
                  for (i = -1, l = pipe.length; ++i < l;
                       ws.push(create(
                           dataObj,
                           pipe[i],
                           onParamChange,
                           self,
                           {
                               filterLookup: self.filterLookup,
                               mock: self.mock
                           }
                       ))) {}
  
                  if (fn.$undeterministic) {
                      opt.undeterm = true;
                  }
  
                  pipes[prepend?"unshift":"push"]([fn, pipe, ws, opt]);
  
                  if (opt.undeterm) {
                      self.deterministic = false;
                  }
              }
          },
  
          _getRawValue: function() {
              var self    = this,
                  val;
  
              if (self.mock) {
                  return self.mock;
              }
  
              switch (self.type) {
                  case "static":
                      val = self.staticValue;
                      break;
  
                  case "attr":
                      val = self.obj[self.code];
                      break;
                  case "expr":
                      val = self.getterFn(self.obj);
                      break;
                  case "object":
                      val = self.obj;
                      break;
              }
  
              if (isArray(val)) {
                  if (!self.inputPipes) {
                      self._indexArrayItems(val);
                  }
                  val = val.slice();
              }
  
              return val;
          },
  
          _getValue: function(useUnfiltered) {
  
              var self    = this,
                  val     = useUnfiltered ? self.unfiltered : self._getRawValue();
  
              self.unfiltered = val;
  
              if (self.mock) {
                  val = self.mock;
              }
              else {
                  val = self._runThroughPipes(val, self.pipes);
              }
  
              return val;
          },
  
  
          _runThroughPipes: function(val, pipes) {
  
              if (pipes) {
                  var j,
                      args,
                      exprs,
                      self    = this,
                      jlen    = pipes.length,
                      dataObj = self.obj,
                      opt,
                      z, zl;
  
                  for (j = 0; j < jlen; j++) {
                      exprs   = pipes[j][1];
                      opt     = pipes[j][3];
                      args    = [];
                      for (z = -1, zl = exprs.length; ++z < zl;
                           args.push(evaluate(exprs[z], dataObj))){}
  
                      args.unshift(dataObj);
                      args.unshift(val);
  
                      val     = pipes[j][0].apply(null, args);
  
                      if (opt.neg) {
                          val = !val;
                      }
                      else if (opt.dblneg) {
                          val = !!val;
                      }
                  }
              }
  
              return val;
          },
  
          /**
           * Subscribe to the change event
           * @method
           * @param {function} fn listener
           * @param {object} fnScope listener's "this" object
           * @param {object} options see Observable's options in on()
           */
          subscribe: function(fn, fnScope, options) {
              observable.on(this.id, fn, fnScope, options);
          },
  
          /**
           * Unsubscribe from change event
           * @param {function} fn
           * @param {object} fnScope
           * @returns {*}
           */
          unsubscribe: function(fn, fnScope) {
              return observable.un(this.id, fn, fnScope);
          },
  
          /**
           * @returns {boolean}
           */
          hasPipes: function() {
              return this.pipes !== null;
          },
  
          /**
           * @returns {boolean}
           */
          hasInputPipes: function() {
              return this.inputPipes != null;
          },
  
          /**
           * @param {function|string} p
           * @returns {boolean}
           */
          hasPipe: function(p) {
              return this._hasPipe(this.pipes, p);
          },
  
          /**
           * @param {function|string} p
           * @returns {boolean}
           */
          hasInputPipe: function(p) {
              return this._hasPipe(this.inputPipes, p);
          },
  
          /**
           * @param {array} pipes
           * @param {function|string} p
           * @returns {boolean}
           */
          _hasPipe: function(pipes, p) {
              if (!pipes) {
                  return false;
              }
              var i, l, name;
              name = isFunction(p) ? p.name : p;
              for (i = 0, l = pipes.length; i < l; i++) {
                  if (pipes[i][3].name === name) {
                      return true;
                  }
              }
              return false;
          },
  
          /**
           * Get current value (filtered and via executing the code)
           * @returns {*}
           */
          getValue: function() {
              return this._getValue();
          },
  
          /**
           * Get last calculated value before filters were applied
           * @returns {*}
           */
          getUnfilteredValue: function() {
              return this.unfiltered || this.curr;
          },
  
          /**
           * Get previous value
           * @returns {*}
           */
          getPrevValue: function() {
              return this.prev;
          },
  
          /**
           * Get last calculated value (with filters and pipes)
           * @returns {*}
           */
          getLastValue: function() {
              return this.curr;
          },
  
          /**
           * Get simple array change prescription
           * @param {[]} from optional
           * @param {[]} to optional
           * @returns {[]}
           */
          getPrescription: function(from, to) {
              to = to || this._getValue();
              return levenshteinArray(from || [], to || []).prescription;
          },
  
          /**
           * Get array change prescription with moves
           * @param {[]} from
           * @param {function} trackByFn
           * @param {[]} to
           * @returns {[]}
           */
          getMovePrescription: function(from, trackByFn, to) {
  
              var self    = this;
                  to      = to || self._getValue();
  
              return prescription2moves(
                  from || [],
                  to || [],
                  self.getPrescription(from || [], to || []),
                  trackByFn
              );
          },
  
          /**
           * Set value to observed property
           * @param {*} val
           */
          setValue: function(val) {
  
              var self    = this,
                  type    = self.type;
  
              self.lastSetValue = val;
  
              val = self._runThroughPipes(val, self.inputPipes);
  
              if (type === "attr") {
                  self.obj[self.code] = val;
              }
              else if (type === "expr") {
  
                  if (!self.setterFn) {
                      self.setterFn   = createSetter(self.code);
                  }
  
                  self.setterFn(self.obj, val);
              }
              else if (type === "object") {
                  self.obj = val;
              }
          },
  
          onInputParamChange: function(val, prev, async) {
              this.setValue(this.lastSetValue);
              if (async) {
                  this.checkAll();
              }
          },
  
          onPipeParamChange: function(val, prev, async) {
              this.forcePipes = true;
              this.check();
              this.forcePipes = false;
          },
  
          /*onObserverChange: function(changes) {
  
              var self = this,
                  code = self.code,
                  i, l,
                  change;
  
              for (i = 0, l = changes.length; i < l; i++) {
                  change = changes[i];
                  if (change.name == code) {
                      self.obsrvChanged = true;
                      break;
                  }
              }
          },*/
  
          _check: function(async) {
  
              var self    = this,
                  val;
  
              if (self.deterministic && self.pipes && !self.forcePipes) {
                  if (!self._checkUnfiltered()) {
                      return false;
                  }
                  else {
                      // code smell.
                      // useUnfiltered param implies that
                      // _checkUnfiltered has been called.
                      val = self._getValue(true);
                  }
              }
              else {
                  val     = self._getValue();
              }
  
              var curr    = self.currCopy,
                  eq      = equals(curr, val);
  
              //if (self.obsrvDelegate) {
              //    eq      = !self.obsrvChanged;
              //}
              //else {
              //    eq      = equals(curr, val);
              //}
  
              if (!eq) {
                  self.curr = val;
                  self.prev = curr;
                  self.currCopy = isPrimitive(val) ? val : copy(val);
                  //self.obsrvChanged = false;
                  observable.trigger(self.id, val, curr, async);
                  return true;
              }
  
              return false;
          },
  
          _checkUnfiltered: function() {
  
              var self    = this,
                  val     = self._getRawValue(),
                  curr    = self.unfilteredCopy,
                  eq      = equals(curr, val);
  
              if (!eq) {
                  self.unfiltered = val;
                  self.unfilteredCopy = isPrimitive(val) ? val : copy(val);
                  return true;
              }
  
              return false;
          },
  
          /**
           * Check for changes
           * @param {bool} async
           * @returns {bool}
           */
          check: function(async) {
              return this._check(async);
          },
  
          /**
           * Check all observed properties for changes
           * @returns {bool}
           */
          checkAll: function() {
              return this.obj.$$watchers.$checkAll();
          },
  
          /**
           * Get last calculated value (with filters and pipes)
           * @returns {*}
           */
          getLastResult: function() {
              return this.curr;
          },
  
          /**
           * Set time interval to check for changes periodically
           * @param {number} ms
           */
          setInterval: function(ms) {
  
              var self    = this;
              if (self.itv) {
                  self.clearInterval();
              }
              self.itv = setInterval(function(){self.check();}, ms);
          },
  
          /**
           * Clear check interval
           * @method
           */
          clearInterval: function() {
              var self    = this;
              if (self.itv) {
                  clearInterval(self.itv);
                  self.itv = null;
              }
          },
  
          /**
           * Unsubscribe and destroy if there are no other listeners
           * @param {function} fn
           * @param {object} fnScope
           * @returns {boolean} true if destroyed
           */
          unsubscribeAndDestroy: function(fn, fnScope) {
  
              var self    = this,
                  id      = self.id;
              
              if (!id) {
                  return false;
              }
  
              if (fn) {
                  observable.un(id, fn, fnScope);
              }
  
              if (!observable.hasListener(id)) {
                  self.destroy();
                  return true;
              }
  
              return false;
          },
  
          /**
           * @method
           */
          destroy: function() {
  
              var self    = this,
                  pipes   = self.pipes,
                  ipipes  = self.inputPipes,
                  i, il,
                  j, jl,
                  ws;
  
              if (self.itv) {
                  self.clearInterval();
              }
  
              if (pipes) {
                  for (i = -1, il = pipes.length; ++i < il;) {
                      ws = pipes[i][2];
                      for (j = -1, jl = ws.length; ++j < jl;) {
                          ws[j].unsubscribeAndDestroy(self.check, self);
                      }
                  }
              }
              if (ipipes) {
                  for (i = -1, il = ipipes.length; ++i < il;) {
                      ws = ipipes[i][2];
                      for (j = -1, jl = ws.length; ++j < jl;) {
                          ws[j].unsubscribeAndDestroy(self.onInputParamChange, self);
                      }
                  }
              }
  
              //if (self.obsrvDelegate) {
              //    Object.unobserve(self.obj, self.obsrvDelegate);
              //}
  
              if (self.obj) {
                  //delete self.obj.$$watchers.$codes[self.origCode];
                  self.obj.$$watchers.$codes[self.origCode] = null;
              }
  
              observable.destroyEvent(self.id);
  
              for (i in self) {
                  if (self.hasOwnProperty(i)){
                      self[i] = null;
                  }
              }
          }
      }, true, false);
  
  
      /**
       * @method
       * @static
       * @param {object} obj
       * @param {string} code
       * @param {function} fn
       * @param {object} fnScope
       * @param {object} opt
       * @returns {Watchable}
       */
      var create = function(obj, code, fn, fnScope, opt) {
  
              opt = opt || {};
              code = code || "";
  
              code = normalizeExpr(obj, trim(code), opt.mock);
  
              if (obj) {
                  if (!obj.$$watchers) {
                      obj.$$watchers = {
                          $codes: {},
                          $checkAll: function() {
  
                              var ws      = this.$codes,
                                  i,
                                  changes = 0;
  
                              for (i in ws) {
  
                                  if (ws[i] && ws[i].check()) {
                                      changes++;
                                  }
                              }
  
                              return changes;
                          },
                          $destroyAll: function() {
  
                              var ws      = this.$codes,
                                  i;
  
                              for (i in ws) {
                                  if (ws[i]) {
                                      ws[i].destroy();
                                      //delete ws[i];
                                      ws[i] = null;
                                  }
                              }
                          }
                      };
                  }
  
                  if (obj.$$watchers.$codes[code]) {
                      obj.$$watchers.$codes[code].subscribe(fn, fnScope,
                          {append: [opt.userData || null], allowDupes: true});
                  }
                  else {
                      obj.$$watchers.$codes[code] = new Watchable(
                          obj, code, fn, fnScope, opt);
                  }
  
                  return obj.$$watchers.$codes[code];
              }
              else {
                  return new Watchable(obj, code, fn, fnScope, opt);
              }
          },
  
          /**
           * @method
           * @static
           * @param {object} obj
           * @param {string} code
           * @param {function} fn
           * @param {object} fnScope
           */
          unsubscribeAndDestroy = function(obj, code, fn, fnScope) {
              code = trim(code);
  
              var ws = obj.$$watchers ? obj.$$watchers.$codes : null;
  
              if (ws && ws[code] && ws[code].unsubscribeAndDestroy(fn, fnScope)) {
                  //delete ws[code];
                  ws[code] = null;
              }
          },
  
          /**
           * Normalize expression
           * @param {object} dataObj
           * @param {string} expr
           * @param {*} mockMode
           * @returns {string}
           */
          normalizeExpr = function(dataObj, expr, mockMode) {
  
              if (expr.substr(0, 2) === '{{') {
                  expr = expr.substring(2, expr.length - 2);
              }
  
              // in mock mode we can't check dataObj for having
              // a property. dataObj does not exists in this
              // context
              if (mockMode) {
                  var match;
                  if ((match = expr.match(/(^|this)\.([A-Z0-9_$]+)$/i)) !== null) {
                      return match[2];
                  }
                  else {
                      return expr;
                  }
              }
  
              if (dataObj && expr) {
                  if (dataObj.hasOwnProperty(expr)) {
                      return expr;
                  }
                  var prop;
                  if (expr.charAt(0) === '.') {
                      prop = expr.substr(1);
                      if (dataObj.hasOwnProperty(prop)) {
                          return prop;
                      }
                  }
                  else if (expr.substr(0, 5) === "this.") {
                      prop = expr.substr(5);
                      if (dataObj.hasOwnProperty(prop)) {
                          return prop;
                      }
                  }
              }
              return expr;
          },
  
          /**
           * Evaluate code against object
           * @param {string} expr
           * @param {object} scope
           * @param {object} opt
           * @returns {*}
           */
          evaluate    = function(expr, scope, opt) {
              var val;
              if (val = isStatic(expr)) {
                  return val;
              }
              if (expr.indexOf('|') === -1) {
                  return createGetter(expr)(scope);
              }
              var w = create(scope, expr, null, null, opt),
                  v = w.getValue();
              w.unsubscribeAndDestroy();
              return v;
          };
  
  
  
      Watchable.create = create;
      Watchable.unsubscribeAndDestroy = unsubscribeAndDestroy;
      Watchable.normalizeExpr = normalizeExpr;
      Watchable.eval = evaluate;
  
      return Watchable;
  }();
  
  
  
  
  
  
  
  var createWatchable = Watchable.create;
  
  
  
  function isObject(value) {
      if (value === null || typeof value != "object") {
          return false;
      }
      var vt = varType(value);
      return vt > 2 || vt == -1;
  };
  
  
  
  var Cache = function(){
  
      var globalCache;
  
      /**
       * @class Cache
       */
  
      /**
       * @constructor
       * @param {bool} cacheRewritable
       */
      var Cache = function(cacheRewritable) {
  
          var storage = {},
  
              finders = [];
  
          if (arguments.length == 0) {
              cacheRewritable = true;
          }
  
          return {
  
              /**
               * @param {function} fn
               * @param {object} context
               * @param {bool} prepend
               */
              addFinder: function(fn, context, prepend) {
                  finders[prepend? "unshift" : "push"]({fn: fn, context: context});
              },
  
              /**
               * @method
               * @param {string} name
               * @param {*} value
               * @param {bool} rewritable
               * @returns {*} value
               */
              add: function(name, value, rewritable) {
  
                  if (storage[name] && storage[name].rewritable === false) {
                      return storage[name];
                  }
  
                  storage[name] = {
                      rewritable: typeof rewritable != strUndef ? rewritable : cacheRewritable,
                      value: value
                  };
  
                  return value;
              },
  
              /**
               * @method
               * @param {string} name
               * @returns {*}
               */
              get: function(name) {
  
                  if (!storage[name]) {
                      if (finders.length) {
  
                          var i, l, res,
                              self = this;
  
                          for (i = 0, l = finders.length; i < l; i++) {
  
                              res = finders[i].fn.call(finders[i].context, name, self);
  
                              if (res !== undf) {
                                  return self.add(name, res, true);
                              }
                          }
                      }
  
                      return undf;
                  }
  
                  return storage[name].value;
              },
  
              /**
               * @method
               * @param {string} name
               * @returns {*}
               */
              remove: function(name) {
                  var rec = storage[name];
                  if (rec && rec.rewritable === true) {
                      delete storage[name];
                  }
                  return rec ? rec.value : undf;
              },
  
              /**
               * @method
               * @param {string} name
               * @returns {boolean}
               */
              exists: function(name) {
                  return !!storage[name];
              },
  
              /**
               * @param {function} fn
               * @param {object} context
               */
              eachEntry: function(fn, context) {
                  var k;
                  for (k in storage) {
                      fn.call(context, storage[k].value, k);
                  }
              },
  
              /**
               * @method
               */
              destroy: function() {
  
                  var self = this;
  
                  if (self === globalCache) {
                      globalCache = null;
                  }
  
                  storage = null;
                  cacheRewritable = null;
  
                  self.add = null;
                  self.get = null;
                  self.destroy = null;
                  self.exists = null;
                  self.remove = null;
              }
          };
      };
  
      /**
       * @method
       * @static
       * @returns {Cache}
       */
      Cache.global = function() {
  
          if (!globalCache) {
              globalCache = new Cache(true);
          }
  
          return globalCache;
      };
  
      return Cache;
  
  }();
  
  
  
  
  
  /**
   * @class Namespace
   * @code ../examples/main.js
   */
  var Namespace = function(){
  
  
      /**
       * @param {Object} root optional; usually window or global
       * @param {String} rootName optional. If you want custom object to be root and
       * this object itself is the first level of namespace
       * @param {Cache} cache optional
       * @constructor
       */
      var Namespace   = function(root, rootName, cache) {
  
          cache       = cache || new Cache(false);
          var self    = this,
              rootL   = rootName ? rootName.length : null;
  
          if (!root) {
              if (typeof global !== strUndef) {
                  root    = global;
              }
              else {
                  root    = window;
              }
          }
  
          var normalize   = function(ns) {
              if (ns && rootName && ns.substr(0, rootL) !== rootName) {
                  return rootName + "." + ns;
              }
              return ns;
          };
  
          var parseNs     = function(ns) {
  
              ns = normalize(ns);
  
              var tmp     = ns.split("."),
                  i,
                  last    = tmp.pop(),
                  parent  = tmp.join("."),
                  len     = tmp.length,
                  name,
                  current = root;
  
  
              if (cache[parent]) {
                  return [cache[parent], last, ns];
              }
  
              if (len > 0) {
                  for (i = 0; i < len; i++) {
  
                      name    = tmp[i];
  
                      if (rootName && i === 0 && name === rootName) {
                          current = root;
                          continue;
                      }
  
                      if (current[name] === undf) {
                          current[name]   = {};
                      }
  
                      current = current[name];
                  }
              }
  
              return [current, last, ns];
          };
  
          /**
           * Get namespace/cache object
           * @method
           * @param {string} ns
           * @param {bool} cacheOnly
           * @returns {*}
           */
          var get       = function(ns, cacheOnly) {
  
              ns = normalize(ns);
  
              if (cache.exists(ns)) {
                  return cache.get(ns);
              }
  
              if (cacheOnly) {
                  return undf;
              }
  
              var tmp     = ns.split("."),
                  i,
                  len     = tmp.length,
                  name,
                  current = root;
  
              for (i = 0; i < len; i++) {
  
                  name    = tmp[i];
  
                  if (rootName && i === 0 && name === rootName) {
                      current = root;
                      continue;
                  }
  
                  if (current[name] === undf) {
                      return undf;
                  }
  
                  current = current[name];
              }
  
              if (current) {
                  cache.add(ns, current);
              }
  
              return current;
          };
  
          /**
           * Register item
           * @method
           * @param {string} ns
           * @param {*} value
           */
          var register    = function(ns, value) {
  
              var parse   = parseNs(ns),
                  parent  = parse[0],
                  name    = parse[1];
  
              if (isObject(parent) && parent[name] === undf) {
  
                  parent[name]        = value;
                  cache.add(parse[2], value);
              }
  
              return value;
          };
  
          /**
           * Item exists
           * @method
           * @param {string} ns
           * @returns boolean
           */
          var exists      = function(ns) {
              return get(ns, true) !== undf;
          };
  
          /**
           * Add item only to the cache
           * @function add
           * @param {string} ns
           * @param {*} value
           */
          var add = function(ns, value) {
  
              ns = normalize(ns);
              cache.add(ns, value);
              return value;
          };
  
          /**
           * Remove item from cache
           * @method
           * @param {string} ns
           */
          var remove = function(ns) {
              ns = normalize(ns);
              cache.remove(ns);
          };
  
          /**
           * Make alias in the cache
           * @method
           * @param {string} from
           * @param {string} to
           */
          var makeAlias = function(from, to) {
  
              from = normalize(from);
              to = normalize(to);
  
              var value = cache.get(from);
  
              if (value !== undf) {
                  cache.add(to, value);
              }
          };
  
          /**
           * Destroy namespace and all classes in it
           * @method
           */
          var destroy     = function() {
  
              var self = this,
                  k;
  
              if (self === globalNs) {
                  globalNs = null;
              }
  
              cache.eachEntry(function(entry){
                  if (entry && entry.$destroy) {
                      entry.$destroy();
                  }
              });
  
              cache.destroy();
              cache = null;
  
              for (k in self) {
                  self[k] = null;
              }
          };
  
          self.register   = register;
          self.exists     = exists;
          self.get        = get;
          self.add        = add;
          self.remove     = remove;
          self.normalize  = normalize;
          self.makeAlias  = makeAlias;
          self.destroy    = destroy;
      };
  
      var p = Namespace.prototype;
  
      p.register = p.exists = p.get = p.add = 
          p.remove = p.normalize = 
          p.makeAlias = p.destroy = null;
      p = null;
  
      var globalNs;
  
      /**
       * Get global namespace
       * @method
       * @static
       * @returns {Namespace}
       */
      Namespace.global = function() {
          if (!globalNs) {
              globalNs = new Namespace;
          }
          return globalNs;
      };
  
      return Namespace;
  
  }();
  
  
  
  
  var ns = new Namespace(MetaphorJs, "MetaphorJs");
  
  
  
  var nsGet = ns.get;
  
  
  
  var filterLookup = function(name) {
      return nsGet("filter." + name, true);
  };
  
  
  
  var instantiate = function(fn, args) {
  
      var Temp = function(){},
          inst, ret;
  
      Temp.prototype  = fn.prototype;
      inst            = new Temp;
      ret             = fn.apply(inst, args);
  
      // If an object has been returned then return it otherwise
      // return the original instance.
      // (consistent with behaviour of the new operator)
      return isObject(ret) || ret === false ? ret : inst;
  
  };
  /**
   * Function interceptor
   * @param {function} origFn
   * @param {function} interceptor
   * @param {object|null} context
   * @param {object|null} origContext
   * @param {string} when
   * @param {bool} replaceValue
   * @returns {Function}
   */
  function intercept(origFn, interceptor, context, origContext, when, replaceValue) {
  
      when = when || "before";
  
      return function() {
  
          var intrRes,
              origRes;
  
          if (when == "instead") {
              return interceptor.apply(context || origContext, arguments);
          }
          else if (when == "before") {
              intrRes = interceptor.apply(context || origContext, arguments);
              origRes = intrRes !== false ? origFn.apply(origContext || context, arguments) : null;
          }
          else {
              origRes = origFn.apply(origContext || context, arguments);
              intrRes = interceptor.apply(context || origContext, arguments);
          }
  
          return replaceValue ? intrRes : origRes;
      };
  };
  
  
  
  var Class = function(){
  
  
      var proto   = "prototype",
  
          constr  = "$constructor",
  
          $constr = function $constr() {
              var self = this;
              if (self.$super && self.$super !== emptyFn) {
                  self.$super.apply(self, arguments);
              }
          },
  
          wrapPrototypeMethod = function wrapPrototypeMethod(parent, k, fn) {
  
              var $super = parent[proto][k] ||
                          (k === constr ? parent : emptyFn) ||
                          emptyFn;
  
              return function() {
                  var ret,
                      self    = this,
                      prev    = self.$super;
  
                  if (self.$destroyed) {
                      self.$super = null;
                      return null;
                  }
  
                  self.$super     = $super;
                  ret             = fn.apply(self, arguments);
                  self.$super     = prev;
  
                  return ret;
              };
          },
  
          preparePrototype = function preparePrototype(prototype, cls, parent, onlyWrap) {
              var k, ck, pk, pp = parent[proto];
  
              for (k in cls) {
                  if (cls.hasOwnProperty(k)) {
                      
                      pk = pp[k];
                      ck = cls[k];
  
                      prototype[k] = isFunction(ck) && (!pk || isFunction(pk)) ?
                                      wrapPrototypeMethod(parent, k, ck) :
                                      ck;
                  }
              }
  
              if (onlyWrap) {
                  return;
              }
  
              prototype.$plugins      = null;
              prototype.$pluginMap    = null;
  
              if (pp.$beforeInit) {
                  prototype.$beforeInit = pp.$beforeInit.slice();
                  prototype.$afterInit = pp.$afterInit.slice();
                  prototype.$beforeDestroy = pp.$beforeDestroy.slice();
                  prototype.$afterDestroy = pp.$afterDestroy.slice();
              }
              else {
                  prototype.$beforeInit = [];
                  prototype.$afterInit = [];
                  prototype.$beforeDestroy = [];
                  prototype.$afterDestroy = [];
              }
          },
          
          mixinToPrototype = function(prototype, mixin) {
              
              var k;
              for (k in mixin) {
                  if (mixin.hasOwnProperty(k)) {
                      if (k === "$beforeInit") {
                          prototype.$beforeInit.push(mixin[k]);
                      }
                      else if (k === "$afterInit") {
                          prototype.$afterInit.push(mixin[k]);
                      }
                      else if (k === "$beforeDestroy") {
                          prototype.$beforeDestroy.push(mixin[k]);
                      }
                      else if (k === "$afterDestroy") {
                          prototype.$afterDestroy.push(mixin[k]);
                      }
                      else if (!prototype[k]) {
                          prototype[k] = mixin[k];
                      }
                  }
              }
          };
  
  
      var Class = function(ns){
  
          if (!ns) {
              ns = new Namespace;
          }
  
          var createConstructor = function(className) {
  
              return function() {
  
                  var self    = this,
                      before  = [],
                      after   = [],
                      args    = arguments,
                      newArgs,
                      i, l,
                      plugins, plugin,
                      pmap,
                      plCls;
  
                  if (!self) {
                      throw "Must instantiate via new: " + className;
                  }
  
                  self.$plugins   = [];
  
                  newArgs = self[constr].apply(self, arguments);
  
                  if (newArgs && isArray(newArgs)) {
                      args = newArgs;
                  }
  
                  plugins = self.$plugins;
                  pmap    = self.$pluginMap = {};
  
                  for (i = -1, l = self.$beforeInit.length; ++i < l;
                       before.push([self.$beforeInit[i], self])) {}
  
                  for (i = -1, l = self.$afterInit.length; ++i < l;
                       after.push([self.$afterInit[i], self])) {}
  
                  if (plugins && plugins.length) {
  
                      for (i = 0, l = plugins.length; i < l; i++) {
  
                          plugin = plugins[i];
  
                          if (isString(plugin)) {
                              plCls = plugin;
                              plugin = ns.get(plugin, true);
                              if (!plugin) {
                                  throw plCls + " not found";
                              }
                          }
   
                          plugin = new plugin(self, args);
                          pmap[plugin.$class] = plugin;
  
                          if (plugin.$beforeHostInit) {
                              before.push([plugin.$beforeHostInit, plugin]);
                          }
                          if (plugin.$afterHostInit) {
                              after.push([plugin.$afterHostInit, plugin]);
                          }
  
                          plugins[i] = plugin;
                      }
                  }
  
                  for (i = -1, l = before.length; ++i < l;
                       before[i][0].apply(before[i][1], args)){}
  
                  if (self.$init) {
                      self.$init.apply(self, args);
                  }
  
                  for (i = -1, l = after.length; ++i < l;
                       after[i][0].apply(after[i][1], args)){}
  
              };
          };
  
  
          /**
           * @class BaseClass
           * @description All classes defined with MetaphorJs.Class extend this class.
           * You can access it via <code>cs.BaseClass</code>. Basically,
           * <code>cs.define({});</code> is the same as <code>cs.BaseClass.$extend({})</code>.
           * @constructor
           */
          var BaseClass = function() {
  
          };
  
          extend(BaseClass.prototype, {
  
              $class: null,
              $extends: null,
              $plugins: null,
              $pluginMap: null,
              $mixins: null,
  
              $destroyed: false,
              $destroying: false,
  
              $constructor: emptyFn,
              $init: emptyFn,
              $beforeInit: [],
              $afterInit: [],
              $beforeDestroy: [],
              $afterDestroy: [],
  
              /**
               * Get class name
               * @method
               * @returns {string}
               */
              $getClass: function() {
                  return this.$class;
              },
  
              /**
               * @param {string} cls
               * @returns {boolean}
               */
              $is: function(cls) {
                  return isInstanceOf(this, cls);
              },
  
              /**
               * Get parent class name
               * @method
               * @returns {string | null}
               */
              $getParentClass: function() {
                  return this.$extends;
              },
  
              /**
               * Intercept method
               * @method
               * @param {string} method Intercepted method name
               * @param {function} fn function to call before or after intercepted method
               * @param {object} newContext optional interceptor's "this" object
               * @param {string} when optional, when to call interceptor before | after | instead; default "before"
               * @param {bool} replaceValue optional, return interceptor's return value or original method's; default false
               * @returns {function} original method
               */
              $intercept: function(method, fn, newContext, when, replaceValue) {
                  var self = this,
                      orig = self[method];
                  self[method] = intercept(orig || emptyFn, fn, newContext || self, self, when, replaceValue);
                  return orig || emptyFn;
              },
  
              /**
               * Implement new methods or properties on instance
               * @param {object} methods
               */
              $implement: function(methods) {
                  var $self = this.constructor;
                  if ($self && $self.$parent) {
                      preparePrototype(this, methods, $self.$parent, true);
                  }
              },
  
              /**
               * Does this instance have a plugin
               * @param cls
               * @returns {boolean}
               */
              $hasPlugin: function(cls) {
                  return !!this.$pluginMap[ns.normalize(cls)];
              },
  
              /**
               * @param {string} cls
               * @returns {object|null}
               */
              $getPlugin: function(cls) {
                  return this.$pluginMap[ns.normalize(cls)] || null;
              },
  
              /**
               * @param {function} fn
               * @returns {Function}
               */
              $bind: function(fn) {
                  var self = this;
                  return function() {
                      if (!self.$isDestroyed()) {
                          return fn.apply(self, arguments);
                      }
                  };
              },
  
              /**
               * @return boolean
               */
              $isDestroyed: function() {
                  return self.$destroying || self.$destroyed;
              },
  
              /**
               * Destroy instance
               * @method
               */
              $destroy: function() {
  
                  var self    = this,
                      before  = self.$beforeDestroy,
                      after   = self.$afterDestroy,
                      plugins = self.$plugins,
                      i, l, res;
  
                  if (self.$destroying || self.$destroyed) {
                      return;
                  }
  
                  self.$destroying = true;
  
                  for (i = -1, l = before.length; ++i < l;
                       before[i].apply(self, arguments)){}
  
                  for (i = 0, l = plugins.length; i < l; i++) {
                      if (plugins[i].$beforeHostDestroy) {
                          plugins[i].$beforeHostDestroy.call(plugins[i], arguments);
                      }
                  }
  
                  res = self.destroy.apply(self, arguments);
  
                  for (i = -1, l = after.length; ++i < l;
                       after[i].apply(self, arguments)){}
  
                  for (i = 0, l = plugins.length; i < l; i++) {
                      plugins[i].$destroy.apply(plugins[i], arguments);
                  }
  
                  if (res !== false) {
                      for (i in self) {
                          if (self.hasOwnProperty(i)) {
                              self[i] = null;
                          }
                      }
                  }
  
                  self.$destroying = false;
                  self.$destroyed = true;
              },
  
              destroy: function(){}
          });
  
          BaseClass.$self = BaseClass;
  
          /**
           * Create an instance of current class. Same as cs.factory(name)
           * @method
           * @static
           * @code var myObj = My.Class.$instantiate(arg1, arg2, ...);
           * @returns {object} class instance
           */
          BaseClass.$instantiate = function() {
  
              var cls = this,
                  args = arguments,
                  cnt = args.length;
  
              // lets make it ugly, but without creating temprorary classes and leaks.
              // and fallback to normal instantiation.
  
              switch (cnt) {
                  case 0:
                      return new cls;
                  case 1:
                      return new cls(args[0]);
                  case 2:
                      return new cls(args[0], args[1]);
                  case 3:
                      return new cls(args[0], args[1], args[2]);
                  case 4:
                      return new cls(args[0], args[1], args[2], args[3]);
                  default:
                      return instantiate(cls, args);
              }
          };
  
          /**
           * Override class methods (on prototype level, not on instance level)
           * @method
           * @static
           * @param {object} methods
           */
          BaseClass.$override = function(methods) {
              var $self = this.$self,
                  $parent = this.$parent;
  
              if ($self && $parent) {
                  preparePrototype($self.prototype, methods, $parent);
              }
          };
  
          /**
           * Create new class based on current one
           * @param {object} definition
           * @param {object} statics
           * @returns {function}
           */
          BaseClass.$extend = function(definition, statics) {
              return defineClass(definition, statics, this);
          };
  
          /**
           * Destroy class
           * @method
           */
          BaseClass.$destroy = function() {
              var self = this,
                  k;
  
              for (k in self) {
                  self[k] = null;
              }
          };
  
          /**
           * @class Class
           */
  
          /**
           * @method Class
           * @constructor
           * @param {Namespace} ns optional namespace. See metaphorjs-namespace repository
           */
  
          /**
           * @method
           * @param {object} definition {
           *  @type {string} $class optional
           *  @type {string} $extends optional
           *  @type {array} $mixins optional
           *  @type {function} $constructor optional
           *  @type {function} $init optional
           *  @type {function} $beforeInit if this is a mixin
           *  @type {function} $afterInit if this is a mixin
           *  @type {function} $beforeHostInit if this is a plugin
           *  @type {function} $afterHostInit if this is a plugin
           *  @type {function} $beforeDestroy if this is a mixin
           *  @type {function} $afterDestroy if this is a mixin
           *  @type {function} $beforeHostDestroy if this is a plugin
           *  @type {function} destroy your own destroy function
           * }
           * @param {object} statics any statis properties or methods
           * @param {string|function} $extends this is a private parameter; use definition.$extends
           * @code var cls = cs.define({$class: "Name"});
           */
          var defineClass = function(definition, statics, $extends) {
  
              definition          = definition || {};
              
              var name            = definition.$class,
                  parentClass     = $extends || definition.$extends,
                  mixins          = definition.$mixins,
                  alias           = definition.$alias,
                  pConstructor,
                  i, l, k, noop, prototype, c, mixin;
  
              if (parentClass) {
                  if (isString(parentClass)) {
                      pConstructor = ns.get(parentClass);
                  }
                  else {
                      pConstructor = parentClass;
                      parentClass = pConstructor.$class || "";
                  }
              }
              else {
                  pConstructor = BaseClass;
                  parentClass = "";
              }
  
              if (parentClass && !pConstructor) {
                  throw parentClass + " not found";
              }
  
              if (name) {
                  name = ns.normalize(name);
              }
  
              definition.$class   = name;
              definition.$extends = parentClass;
              definition.$mixins  = null;
  
              //noop                = function(){};
              //noop[proto]         = pConstructor[proto];
              //prototype           = new noop;
              //noop                = null;
              prototype           = Object.create(pConstructor[proto]);
              definition[constr]  = definition[constr] || $constr;
  
              preparePrototype(prototype, definition, pConstructor);
  
              if (mixins) {
                  for (i = 0, l = mixins.length; i < l; i++) {
                      mixin = mixins[i];
                      if (isString(mixin)) {
                          mixin = ns.get(mixin, true);
                      }
                      mixinToPrototype(prototype, mixin);
                  }
              }
  
              c = createConstructor(name);
              prototype.constructor = c;
              prototype.$self = c;
              c[proto] = prototype;
  
              for (k in BaseClass) {
                  if (k !== proto && BaseClass.hasOwnProperty(k)) {
                      c[k] = BaseClass[k];
                  }
              }
  
              for (k in pConstructor) {
                  if (k !== proto && pConstructor.hasOwnProperty(k)) {
                      c[k] = pConstructor[k];
                  }
              }
  
              if (statics) {
                  for (k in statics) {
                      if (k !== proto && statics.hasOwnProperty(k)) {
                          c[k] = statics[k];
                      }
                  }
              }
  
              c.$parent   = pConstructor;
              c.$self     = c;
  
              if (name) {
                  ns.register(name, c);
              }
              if (alias) {
                  ns.register(alias, c);
              }
  
              return c;
          };
  
  
  
  
          /**
           * Instantiate class. Pass constructor parameters after "name"
           * @method
           * @code cs.factory("My.Class.Name", arg1, arg2, ...);
           * @param {string} name Full name of the class
           * @returns {object} class instance
           */
          var factory = function(name) {
  
              var cls     = ns.get(name),
                  args    = slice.call(arguments, 1);
  
              if (!cls) {
                  throw name + " not found";
              }
  
              return cls.$instantiate.apply(cls, args);
          };
  
  
  
          /**
           * Is cmp instance of cls
           * @method
           * @code cs.instanceOf(myObj, "My.Class");
           * @code cs.instanceOf(myObj, My.Class);
           * @param {object} cmp
           * @param {string|object} cls
           * @returns {boolean}
           */
          var isInstanceOf = function(cmp, cls) {
              var _cls    = isString(cls) ? ns.get(cls) : cls;
              return _cls ? cmp instanceof _cls : false;
          };
  
  
  
          /**
           * Is one class subclass of another class
           * @method
           * @code cs.isSubclassOf("My.Subclass", "My.Class");
           * @code cs.isSubclassOf(myObj, "My.Class");
           * @code cs.isSubclassOf("My.Subclass", My.Class);
           * @code cs.isSubclassOf(myObj, My.Class);
           * @param {string|object} childClass
           * @param {string|object} parentClass
           * @return {boolean}
           */
          var isSubclassOf = function(childClass, parentClass) {
  
              var p   = childClass,
                  g   = ns.get;
  
              if (!isString(parentClass)) {
                  parentClass  = parentClass.prototype.$class;
              }
              else {
                  parentClass = ns.normalize(parentClass);
              }
              if (isString(childClass)) {
                  p   = g(ns.normalize(childClass));
              }
  
              while (p && p.prototype) {
  
                  if (p.prototype.$class === parentClass) {
                      return true;
                  }
  
                  p = p.$parent;
              }
  
              return false;
          };
  
          var self    = this;
  
          self.factory = factory;
          self.isSubclassOf = isSubclassOf;
          self.isInstanceOf = isInstanceOf;
          self.define = defineClass;
  
          self.destroy = function(){
  
              if (self === globalCs) {
                  globalCs = null;
              }
  
              BaseClass.$destroy();
              BaseClass = null;
  
              ns.destroy();
              ns = null;
  
              Class = null;
  
          };
  
          /**
           * @type {BaseClass} BaseClass reference to the BaseClass class
           */
          self.BaseClass = BaseClass;
  
      };
  
      Class.prototype = {
  
          factory: null,
          isSubclassOf: null,
          isInstanceOf: null,
          define: null,
          destroy: null
      };
  
      var globalCs;
  
      /**
       * Get default global class manager
       * @method
       * @static
       * @returns {Class}
       */
      Class.global = function() {
          if (!globalCs) {
              globalCs = new Class(Namespace.global());
          }
          return globalCs;
      };
  
      return Class;
  
  }();
  
  
  
  
  var cs = new Class(ns);
  
  
  
  
  
  var defineClass = cs.define;
  
  
  
  var nsAdd = ns.add;
  
  
  
  
  
  var Directive = function(){
  
      var attributes          = [],
          attributesSorted    = false,
  
          compare             = function(a, b) {
              //if (a is less than b by some ordering criterion)
              if (a.priority < b.priority) {
                  return -1;
              }
  
              //if (a is greater than b by the ordering criterion)
              if (a.priority > b.priority) {
                  return 1;
              }
  
              // a must be equal to b
              return 0;
          },
  
          commentHolders      = function(node, name) {
  
              name = name || "";
  
              var before = window.document.createComment(name + " - start"),
                  after = window.document.createComment(name + " - end"),
                  parent = node.parentNode;
  
              parent.insertBefore(before, node);
  
              if (node.nextSibling) {
                  parent.insertBefore(after, node.nextSibling);
              }
              else {
                  parent.appendChild(after);
              }
  
              return [before, after];
          };
  
      return defineClass({
  
          $class: "Directive",
  
          watcher: null,
          stateFn: null,
          scope: null,
          node: null,
          expr: null,
          mods: null,
  
          autoOnChange: true,
  
          $init: function(scope, node, expr, renderer, attr) {
  
              var self        = this,
                  config      = attr ? attr.config : {},
                  val;
  
              expr            = trim(expr);
  
              self.node       = node;
              self.expr       = expr;
              self.scope      = scope;
              self.saveState  = config.saveState;
              self.watcher    = createWatchable(scope, expr, self.onChange, self, {filterLookup: filterLookup});
  
              if (self.saveState) {
                  self.stateFn = createSetter(self.saveState);
              }
  
              if (self.autoOnChange && (val = self.watcher.getLastResult()) !== undf) {
                  self.onChange(val, undf);
              }
  
              scope.$on("destroy", self.onScopeDestroy, self);
              scope.$on("reset", self.onScopeReset, self);
          },
  
          getChildren: function() {
              return null;
          },
  
          createCommentHolders: function(node, name) {
              var cmts = commentHolders(node, name || this.$class);
              this.prevEl = cmts[0];
              this.nextEl = cmts[1];
          },
  
          onScopeDestroy: function() {
              this.$destroy();
          },
  
          onScopeReset: function() {
  
          },
  
          onChange: function(val) {
              this.saveStateOnChange(val);
          },
  
          saveStateOnChange: function(val) {
              if (this.stateFn) {
                  this.stateFn(this.scope, val);
              }
          },
  
          destroy: function() {
              var self    = this;
  
              if (self.scope) {
                  self.scope.$un("destroy", self.onScopeDestroy, self);
                  self.scope.$un("reset", self.onScopeReset, self);
              }
  
              if (self.watcher) {
                  self.watcher.unsubscribeAndDestroy(self.onChange, self);
              }
  
              if (self.prevEl) {
                  self.prevEl.parentNode.removeChild(self.prevEl);
              }
              if (self.nextEl) {
                  self.nextEl.parentNode.removeChild(self.nextEl);
              }
  
              self.$super();
          }
      }, {
  
          getDirective: function(type, name) {
              return nsGet("directive." + type +"."+ name, true);
          },
  
          registerAttribute: function registerAttribute(name, priority, handler) {
              if (!nsGet("directive.attr." + name, true)) {
                  attributes.push({
                      priority: priority,
                      name: name,
                      handler: nsAdd("directive.attr." + name, handler)
                  });
                  attributesSorted = false;
              }
          },
  
          getAttributes: function getAttributes() {
              if (!attributesSorted) {
                  attributes.sort(compare);
                  attributesSorted = true;
              }
              return attributes;
          },
  
          registerTag: function registerTag(name, handler) {
              if (!nsGet("directive.tag." + name, true)) {
                  nsAdd("directive.tag." + name, handler)
              }
          },
  
          // components are case sensitive
          registerComponent: function(name, cmp) {
              if (!cmp) {
                  cmp = name;
              }
              if (isString(cmp)) {
                  cmp = nsGet(cmp, true);
              }
              if (!nsGet("directive.component." + name, true)) {
                  nsAdd("directive.component." + name.toLowerCase(), cmp)
              }
          },
  
          commentHolders: commentHolders
      });
  
  }();
  
  
  
  
  
  
  
  
  Directive.registerAttribute("app", 100, returnFalse);
  
  function isField(el) {
      var tag	= el.nodeName.toLowerCase(),
          type = el.type;
      if (tag == 'input' || tag == 'textarea' || tag == 'select') {
          if (type != "submit" && type != "reset" && type != "button") {
              return true;
          }
      }
      return false;
  };
  
  function isNull(value) {
      return value === null;
  };
  
  
  
  
  
  var TextRenderer = function(){
  
      var startSymbol             = '{{',
          endSymbol               = '}}',
          startSymbolLength       = 2,
          endSymbolLength         = 2,
  
          savedBoundary           = '--##--',
  
          rReplaceEscape          = /\\{/g,
  
          observer                = new Observable,
  
          //parent, userData, recursive
          factory                 = function(scope, origin, opt) {
  
              if (!origin || !origin.indexOf ||
                  (origin.indexOf(startSymbol) === -1 &&
                   origin.indexOf(savedBoundary) === -1)) {
  
                  if (opt.force) {
                      return new TextRenderer(
                          scope,
                          startSymbol + origin + endSymbol,
                          opt
                      );
                  }
  
                  return null;
              }
  
              return new TextRenderer(scope, origin, opt);
          };
  
      var TextRenderer = defineClass({
  
          $class: "TextRenderer",
  
          id: null,
          parent: null,
          isRoot: null,
          scope: null,
          origin: "",
          processed: null,
          text: null,
          watchers: null,
          children: null,
          data: null,
          recursive: false,
          dataChangeDelegate: null,
          changeTmt: null,
          lang: null,
          boundary: null,
          mock: null,
  
          //parent, userData, recursive, boundary, mock
          $init: function(scope, origin, opt) {
  
              opt = opt || {};
  
              var self        = this;
  
              self.id         = nextUid();
              self.origin     = origin;
              self.scope      = scope;
              self.parent     = opt.parent;
              self.isRoot     = !opt.parent;
              self.data       = opt.userData;
              self.lang       = scope.$app ? scope.$app.lang : null;
              self.boundary   = opt.boundary || "---";
              self.mock       = opt.mock;
  
              if (opt.recursive === true || opt.recursive === false) {
                  self.recursive = opt.recursive;
              }
  
              self.watchers   = [];
              self.children   = [];
  
              self.dataChangeDelegate = bind(self.doDataChange, self);
              self.processed  = self.processText(origin, self.mock);
              self.render();
          },
  
          subscribe: function(fn, context) {
              return observer.on(this.id, fn, context);
          },
  
          unsubscribe: function(fn, context) {
              return observer.un(this.id, fn, context);
          },
  
          getString: function() {
              var self = this;
  
              if (isNull(self.text)) {
                  self.render();
              }
  
              var text = self.text || "";
  
              if (text.indexOf('\\{') !== -1) {
                  return text.replace(rReplaceEscape, '{');
              }
  
              return text;
          },
  
  
          render: function() {
  
              var self    = this,
                  text    = self.processed,
                  b       = self.boundary,
                  i, l,
                  str,
                  ch;
  
              if (!self.children.length) {
                  self.createChildren();
              }
  
              ch = self.children;
  
              for (i = -1, l = ch.length; ++i < l;) {
                  str = ch[i] instanceof TextRenderer ? ch[i].getString() : ch[i];
                  text = text.replace(
                      b + i + b,
                      str === null ? "" : str
                  );
              }
  
              self.text = text;
  
              return text;
          },
  
  
  
          processText: function(text, mock) {
  
              /*
               arguably, str += "" is faster than separators.push() + separators.join()
               well, at least in my Firefox it is so.
               */
  
              var self        = this,
                  index       = 0,
                  textLength  = text.length,
                  startIndex,
                  endIndex,
                  result      = "";
  
              while(index < textLength) {
                  if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
                      ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1) &&
                      text.substr(startIndex - 1, 1) !== '\\') {
  
                      result += text.substring(index, startIndex);
  
                      if (endIndex !== startIndex + startSymbolLength) {
                          result += self.watcherMatch(
                              text.substring(startIndex + startSymbolLength, endIndex),
                              mock
                          );
                      }
  
                      index = endIndex + endSymbolLength;
  
                  } else {
                      // we did not find an interpolation
                      if (index !== textLength) {
                          result += text.substring(index);
                      }
                      break;
                  }
              }
  
  
              //saved keys
              /*index       = 0;
              text        = result;
              textLength  = text.length;
              result      = "";
              var bndLen      = savedBoundary.length,
                  getterid;
  
              while(index < textLength) {
                  if (((startIndex = text.indexOf(savedBoundary, index)) !== -1) &&
                      (endIndex = text.indexOf(savedBoundary, startIndex + bndLen)) !== -1) {
  
                      result += text.substring(index, startIndex);
  
                      getterid    = text.substring(startIndex, endIndex + bndLen);
                      getterid    = getterid.replace(savedBoundary, "");
                      getterid    = parseInt(getterid);
  
                      result += self.watcherMatch(
                          getterid,
                          mock
                      );
  
                      index = endIndex + bndLen;
                  } else {
                      // we did not find an interpolation
                      if (index !== textLength) {
                          result += text.substring(index);
                      }
                      break;
                  }
              }*/
  
              return result;
          },
  
          watcherMatch: function(expr, mock) {
  
              var self        = this,
                  ws          = self.watchers,
                  b           = self.boundary,
                  w,
                  isLang      = false,
                  recursive   = self.recursive,
                  getter      = null;
  
              expr        = trim(expr);
  
              if (expr.substr(0,1) === '-') {
                  var inx = expr.indexOf(" "),
                      mods = expr.substr(1,inx);
                  expr = expr.substr(inx);
  
                  if (!recursive && mods.indexOf("r") !== -1) {
                      recursive = true;
                  }
                  if (mods.indexOf("l") !== -1) {
                      isLang = true;
                  }
              }
  
              /*if (typeof expr === "number") {
                  var getterId = expr;
                  if (typeof __MetaphorJsPrebuilt !== "undefined") {
                      expr = __MetaphorJsPrebuilt['__tpl_getter_codes'][getterId];
                      getter = __MetaphorJsPrebuilt['__tpl_getters'][getterId];
                  }
                  else {
                      return "";
                  }
              }*/
  
              w = createWatchable(
                  self.scope,
                  expr,
                  self.onDataChange,
                  self,
                  {
                      filterLookup: filterLookup, mock: mock, getterFn: getter,
                      userData: {
                          recursive: recursive
                      }
                  }
              );
  
              if (isLang && !w.hasPipe("l")) {
                  w.addPipe("l");
              }
  
              ws.push(w);
  
              return b + (ws.length-1) + b;
          },
  
          onDataChange: function() {
  
              var self    = this;
  
              if (!self.changeTmt) {
                  self.changeTmt = setTimeout(self.dataChangeDelegate, 0);
              }
          },
  
          doDataChange: function() {
              var self = this;
              self.destroyChildren();
              self.triggerChange();
              self.changeTmt = null;
          },
  
          triggerChange: function() {
  
              var self    = this;
              self.text   = null;
  
              if (self.isRoot) {
                  observer.trigger(self.id, self, self.data);
              }
              else {
                  self.parent.triggerChange();
              }
          },
  
  
          createChildren: function() {
  
              var self    = this,
                  ws      = self.watchers,
                  ch      = self.children,
                  scope   = self.scope,
                  rec     = false,
                  i, l,
                  val;
  
              for (i = -1, l = ws.length; ++i < l; ){
                  val     = ws[i].getLastResult();
  
                  //TODO: watcher must have userData!
                  // if it doesn't, it was taken from cache and it is wrong
                  // because -rl flags may be different
                  rec     = self.recursive || (ws[i].userData && ws[i].userData.recursive);
                  if (val === undf) {
                      val = "";
                  }
                  ch.push((rec && factory(scope, val, {parent: self, recursive: true})) || val);
              }
          },
  
          destroyChildren: function() {
  
              var self    = this,
                  ch      = self.children,
                  i, l;
  
              for (i = -1, l = ch.length; ++i < l; ){
                  if (ch[i] instanceof TextRenderer) {
                      ch[i].$destroy();
                  }
              }
  
              self.children = [];
          },
  
          destroyWatchers: function() {
  
              var self    = this,
                  ws      = self.watchers,
                  i, l;
  
              for (i = -1, l = ws.length; ++i < l;
                   ws[i].unsubscribeAndDestroy(self.onDataChange, self)){}
  
              self.watchers = [];
          },
  
          destroy: function() {
  
              var self = this;
  
              self.destroyChildren();
              self.destroyWatchers();
  
              observer.destroyEvent(self.id);
  
              if (self.changeTmt) {
                  clearTimeout(self.changeTmt);
              }
          }
  
      }, {
          create: factory
      });
  
      return TextRenderer;
  }();
  
  
  
  
  
  
  
  var createFunc = functionFactory.createFunc;
  
  
  
  var Scope = function(cfg) {
      var self    = this;
  
      self.$$observable    = new Observable;
      self.$$historyWatchers  = {};
      extend(self, cfg, true, false);
  
      if (self.$parent) {
          self.$parent.$on("check", self.$$onParentCheck, self);
          self.$parent.$on("destroy", self.$$onParentDestroy, self);
          self.$parent.$on("freeze", self.$freeze, self);
          self.$parent.$on("unfreeze", self.$unfreeze, self);
      }
      else {
          self.$root  = self;
          self.$isRoot= true;
      }
  };
  
  extend(Scope.prototype, {
  
      $app: null,
      $parent: null,
      $root: null,
      $isRoot: false,
      $level: 0,
      $static: false,
      $$frozen: false,
      $$observable: null,
      $$watchers: null,
      $$historyWatchers: null,
      $$checking: false,
      $$destroyed: false,
      $$changing: false,
  
      $$tmt: null,
  
      $new: function(data) {
          var self = this;
          return new Scope(extend({}, data, {
              $parent: self,
              $root: self.$root,
              $app: self.$app,
              $level: self.$level + 1,
              $static: self.$static
          }, true, false));
      },
  
      $newIsolated: function() {
          return new Scope({
              $app: this.$app,
              $level: self.$level + 1,
              $static: this.$static
          });
      },
  
      $freeze: function() {
          var self = this;
          if (!self.$$frozen) {
              self.$$frozen = true;
              self.$$observable.trigger("freeze", self);
          }
      },
  
      $unfreeze: function() {
          var self = this;
          if (self.$$frozen) {
              self.$$frozen = false;
              self.$$observable.trigger("unfreeze", self);
          }
      },
  
      $on: function(event, fn, fnScope) {
          return this.$$observable.on(event, fn, fnScope);
      },
  
      $un: function(event, fn, fnScope) {
          return this.$$observable.un(event, fn, fnScope);
      },
  
      $watch: function(expr, fn, fnScope) {
          return Watchable.create(this, expr, fn, fnScope);
      },
  
      $unwatch: function(expr, fn, fnScope) {
          return Watchable.unsubscribeAndDestroy(this, expr, fn, fnScope);
      },
  
      $createGetter: function(expr) {
          var self    = this,
              getter  = createGetter(expr);
          return function() {
              return getter(self);
          };
      },
  
      $createSetter: function(expr) {
          var self    = this,
              setter  = createSetter(expr);
          return function(value) {
              return setter(value, self);
          };
      },
  
      $createFunc: function(expr) {
          var self    = this,
              fn      = createFunc(expr);
          return function() {
              return fn(self);
          };
      },
  
      $watchHistory: function(prop, param) {
          var self = this;
          if (!self.$$historyWatchers[param]) {
              self.$$historyWatchers[param] = prop;
              MetaphorJs.history.on("change-" + param, self.$$onHistoryChange, self);
          }
      },
  
      $unwatchHistory: function(param) {
          var self = this;
          if (!self.$$historyWatchers[param]) {
              delete self.$$historyWatchers[param];
              MetaphorJs.history.un("change-" + param, self.$$onHistoryChange, self);
          }
      },
  
      $wrap: function(fn, context) {
          var self = this,
              name;
  
          if (typeof fn === "string") {
              name = fn;
              fn = context[name];
          }
  
          var wrapper = function() {
              var res = fn.apply(context, arguments);
              self.$check();
              return res;
          };
  
          if (name) {
              context[name] = wrapper;
          }
  
          return wrapper;
      },
  
      $get: function(key) {
  
          var s = this;
  
          while (s) {
              if (s[key] !== undf) {
                  return s[key];
              }
              s = s.$parent;
          }
  
          return undf;
      },
  
      $set: function(key, value) {
          var self = this;
          if (typeof key === "string") {
              this[key] = value;
          }
          else {
              for (var k in key) {
                  self[k] = key[k];
              }
          }
          this.$check();
      },
  
      $$onParentDestroy: function() {
          this.$destroy();
      },
  
      $$onParentCheck: function() {
          this.$check();
      },
  
      $$onHistoryChange: function(val, prev, name) {
          var self = this,
              prop;
          if (self.$$historyWatchers[name]) {
              prop = self.$$historyWatchers[name];
              self[prop] = val;
              self.$check();
          }
      },
  
      $scheduleCheck: function(timeout) {
          var self = this;
          if (!self.$$tmt) {
              self.$tmt = async(self.$check, self, null, timeout);
          }
      },
  
      $check: function() {
          var self = this,
              changes;
  
          if (self.$$checking || self.$static || self.$$frozen) {
              return;
          }
          self.$$checking = true;
  
          if (self.$$tmt) {
              clearTimeout(self.$$tmt);
              self.$$tmt = null;
          }
  
          if (self.$$watchers) {
              changes = self.$$watchers.$checkAll();
          }
  
          self.$$checking = false;
  
          if (!self.$$destroyed) {
              self.$$observable.trigger("check", changes);
          }
  
          if (changes > 0) {
              self.$$changing = true;
              self.$check();
          }
          else {
              // finished changing after all iterations
              if (self.$$changing) {
                  self.$$changing = false;
                  self.$$observable.trigger("changed");
              }
          }
      },
  
      $reset: function(resetVars) {
          var self = this;
          self.$$observable.trigger("reset");
      },
  
      $destroy: function() {
  
          var self    = this,
              param, i;
  
          if (self.$$destroyed) {
              return;
          }
  
          self.$$destroyed = true;
          self.$$observable.trigger("destroy");
          self.$$observable.destroy();
  
          if (self.$parent && self.$parent.$un) {
              self.$parent.$un("check", self.$$onParentCheck, self);
              self.$parent.$un("destroy", self.$$onParentDestroy, self);
              self.$parent.$un("freeze", self.$freeze, self);
              self.$parent.$un("unfreeze", self.$unfreeze, self);
          }
  
          if (self.$$watchers) {
              self.$$watchers.$destroyAll();
          }
  
          for (param in self.$$historyWatchers) {
              self.$unwatchHistory(param);
          }
  
          for (i in self) {
              if (self.hasOwnProperty(i)) {
                  self[i] = null;
              }
          }
  
          self.$$destroyed = true;
      }
  
  }, true, false);
  
  
  
  
  
  
  /**
   * @param {Element} elem
   */
  var getValue = function(){
  
  
      var rreturn = /\r/,
  
          hooks = {
  
          option: function(elem) {
              var val = elem.getAttribute("value") || elem.value;
  
              return val !== undf ?
                     val :
                     trim( elem.innerText || elem.textContent );
          },
  
          select: function(elem) {
  
              var value, option,
                  options = elem.options,
                  index = elem.selectedIndex,
                  one = elem.type === "select-one" || index < 0,
                  values = one ? null : [],
                  max = one ? index + 1 : options.length,
                  disabled,
                  i = index < 0 ?
                      max :
                      one ? index : 0;
  
              // Loop through all the selected options
              for ( ; i < max; i++ ) {
                  option = options[ i ];
  
                  disabled = option.disabled ||
                             option.parentNode.disabled;
  
                  // IE6-9 doesn't update selected after form reset (#2551)
                  if ((option.selected || i === index) && !disabled ) {
                      // Get the specific value for the option
                      value = getValue(option);
  
                      // We don't need an array for one selects
                      if ( one ) {
                          return value;
                      }
  
                      // Multi-Selects return an array
                      values.push( value );
                  }
              }
  
              return values;
          },
  
          radio: function( elem ) {
              return isNull(elem.getAttribute("value")) ? "on" : elem.value;
          },
  
          checkbox: function( elem ) {
              return isNull(elem.getAttribute("value")) ? "on" : elem.value;
          }
      };
  
      return function(elem) {
  
          var hook, ret;
  
          hook = hooks[elem.type] || hooks[elem.nodeName.toLowerCase()];
  
          if (hook && (ret = hook(elem, "value")) !== undf) {
              return ret;
          }
  
          ret = elem.value;
  
          return isString(ret) ?
              // Handle most common string cases
                 ret.replace(rreturn, "") :
              // Handle cases where value is null/undef or number
                 ret == null ? "" : ret;
  
      };
  }();
  
  
  var aIndexOf = (function(){
  
      var aIndexOf    = Array.prototype.indexOf;
  
      if (!aIndexOf) {
          aIndexOf = Array.prototype.indexOf = function (searchElement, fromIndex) {
  
              var k;
  
              // 1. Let O be the result of calling ToObject passing
              //    the this value as the argument.
              if (this == null) {
                  throw new TypeError('"this" is null or not defined');
              }
  
              var O = Object(this);
  
              // 2. Let lenValue be the result of calling the Get
              //    internal method of O with the argument "length".
              // 3. Let len be ToUint32(lenValue).
              var len = O.length >>> 0;
  
              // 4. If len is 0, return -1.
              if (len === 0) {
                  return -1;
              }
  
              // 5. If argument fromIndex was passed let n be
              //    ToInteger(fromIndex); else let n be 0.
              var n = +fromIndex || 0;
  
              if (Math.abs(n) === Infinity) {
                  n = 0;
              }
  
              // 6. If n >= len, return -1.
              if (n >= len) {
                  return -1;
              }
  
              // 7. If n >= 0, then Let k be n.
              // 8. Else, n<0, Let k be len - abs(n).
              //    If k is less than 0, then let k be 0.
              k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
  
              // 9. Repeat, while k < len
              while (k < len) {
                  var kValue;
                  // a. Let Pk be ToString(k).
                  //   This is implicit for LHS operands of the in operator
                  // b. Let kPresent be the result of calling the
                  //    HasProperty internal method of O with argument Pk.
                  //   This step can be combined with c
                  // c. If kPresent is true, then
                  //    i.  Let elementK be the result of calling the Get
                  //        internal method of O with the argument ToString(k).
                  //   ii.  Let same be the result of applying the
                  //        Strict Equality Comparison Algorithm to
                  //        searchElement and elementK.
                  //  iii.  If same is true, return k.
                  if (k in O && O[k] === searchElement) {
                      return k;
                  }
                  k++;
              }
              return -1;
          };
      }
  
      return aIndexOf;
  }());
  
  
  
  /**
   * @param {*} val
   * @param {[]} arr
   * @returns {boolean}
   */
  function inArray(val, arr) {
      return arr ? (aIndexOf.call(arr, val) !== -1) : false;
  };
  
  
  
  function isNumber(value) {
      return varType(value) === 1;
  };
  
  function getAttr(el, name) {
      return el.getAttribute ? el.getAttribute(name) : null;
  };
  
  function setAttr(el, name, value) {
      return el.setAttribute(name, value);
  };
  
  function removeAttr(el, name) {
      return el.removeAttribute(name);
  };
  
  
  
  /**
   * @param {Element} el
   * @param {*} val
   */
  var setValue = function() {
  
      var hooks = {
          select:  function(elem, value) {
  
              var optionSet, option,
                  options     = elem.options,
                  values      = toArray(value),
                  i           = options.length,
                  selected,
                  setIndex    = -1;
  
              while ( i-- ) {
                  option      = options[i];
                  selected    = inArray(option.value, values);
  
                  if (selected) {
                      setAttr(option, "selected", "selected");
                      option.selected = true;
                      optionSet = true;
                  }
                  else {
                      removeAttr(option, "selected");
                  }
  
                  if (!selected && !isNull(getAttr(option, "default-option"))) {
                      setIndex = i;
                  }
              }
  
              // Force browsers to behave consistently when non-matching value is set
              if (!optionSet) {
                  elem.selectedIndex = setIndex;
              }
  
              return values;
          }
      };
  
      hooks["radio"] = hooks["checkbox"] = function(elem, value) {
          if (isArray(value) ) {
              return (elem.checked = inArray(getValue(elem), value));
          }
      };
  
  
      return function(el, val) {
  
          if (el.nodeType !== 1) {
              return;
          }
  
          // Treat null/undefined as ""; convert numbers to string
          if (isNull(val)) {
              val = "";
          }
          else if (isNumber(val)) {
              val += "";
          }
  
          var hook = hooks[el.type] || hooks[el.nodeName.toLowerCase()];
  
          // If set returns undefined, fall back to normal setting
          if (!hook || hook(el, val, "value") === undf) {
              el.value = val;
          }
      };
  }();
  
  
  function returnTrue() {
      return true;
  };
  
  
  
  // from jQuery
  
  var DomEvent = function(src) {
  
      if (src instanceof DomEvent) {
          return src;
      }
  
      // Allow instantiation without the 'new' keyword
      if (!(this instanceof DomEvent)) {
          return new DomEvent(src);
      }
  
  
      var self    = this;
  
      for (var i in src) {
          if (!self[i]) {
              try {
                  self[i] = src[i];
              }
              catch (thrownError){}
          }
      }
  
  
      // Event object
      self.originalEvent = src;
      self.type = src.type;
  
      if (!self.target && src.srcElement) {
          self.target = src.srcElement;
      }
  
  
      var eventDoc, doc, body,
          button = src.button;
  
      // Calculate pageX/Y if missing and clientX/Y available
      if (self.pageX === undf && !isNull(src.clientX)) {
          eventDoc = self.target ? self.target.ownerDocument || window.document : window.document;
          doc = eventDoc.documentElement;
          body = eventDoc.body;
  
          self.pageX = src.clientX +
                        ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
                        ( doc && doc.clientLeft || body && body.clientLeft || 0 );
          self.pageY = src.clientY +
                        ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
                        ( doc && doc.clientTop  || body && body.clientTop  || 0 );
      }
  
      // Add which for click: 1 === left; 2 === middle; 3 === right
      // Note: button is not normalized, so don't use it
      if ( !self.which && button !== undf ) {
          self.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
      }
  
      // Events bubbling up the document may have been marked as prevented
      // by a handler lower down the tree; reflect the correct value.
      self.isDefaultPrevented = src.defaultPrevented ||
                                src.defaultPrevented === undf &&
                                    // Support: Android<4.0
                                src.returnValue === false ?
                                returnTrue :
                                returnFalse;
  
  
      // Create a timestamp if incoming event doesn't have one
      self.timeStamp = src && src.timeStamp || (new Date).getTime();
  };
  
  // Event is based on DOM3 Events as specified by the ECMAScript Language Binding
  // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
  extend(DomEvent.prototype, {
  
      isDefaultPrevented: returnFalse,
      isPropagationStopped: returnFalse,
      isImmediatePropagationStopped: returnFalse,
  
      preventDefault: function() {
          var e = this.originalEvent;
  
          this.isDefaultPrevented = returnTrue;
          e.returnValue = false;
  
          if ( e && e.preventDefault ) {
              e.preventDefault();
          }
      },
      stopPropagation: function() {
          var e = this.originalEvent;
  
          this.isPropagationStopped = returnTrue;
          e.cancelBubble = true;
  
          if ( e && e.stopPropagation ) {
              e.stopPropagation();
          }
      },
      stopImmediatePropagation: function() {
          var e = this.originalEvent;
  
          this.isImmediatePropagationStopped = returnTrue;
  
          if ( e && e.stopImmediatePropagation ) {
              e.stopImmediatePropagation();
          }
  
          this.stopPropagation();
      }
  }, true, false);
  
  
  
  
  function normalizeEvent(originalEvent) {
      return new DomEvent(originalEvent);
  };
  
  
  // from jquery.mousewheel plugin
  
  
  
  var mousewheelHandler = function(e) {
  
      function shouldAdjustOldDeltas(orgEvent, absDelta) {
          // If this is an older event and the delta is divisable by 120,
          // then we are assuming that the browser is treating this as an
          // older mouse wheel event and that we should divide the deltas
          // by 40 to try and get a more usable deltaFactor.
          // Side note, this actually impacts the reported scroll distance
          // in older browsers and can cause scrolling to be slower than native.
          // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
          return orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
      }
  
      function nullLowestDelta() {
          lowestDelta = null;
      }
  
      var toBind = ( 'onwheel' in window.document || window.document.documentMode >= 9 ) ?
                   ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
          nullLowestDeltaTimeout, lowestDelta;
  
      var mousewheelHandler = function(fn) {
  
          return function(e) {
  
              var event = normalizeEvent(e || window.event),
                  args = slice.call(arguments, 1),
                  delta = 0,
                  deltaX = 0,
                  deltaY = 0,
                  absDelta = 0,
                  offsetX = 0,
                  offsetY = 0;
  
  
              event.type = 'mousewheel';
  
              // Old school scrollwheel delta
              if ('detail'      in event) { deltaY = event.detail * -1; }
              if ('wheelDelta'  in event) { deltaY = event.wheelDelta; }
              if ('wheelDeltaY' in event) { deltaY = event.wheelDeltaY; }
              if ('wheelDeltaX' in event) { deltaX = event.wheelDeltaX * -1; }
  
              // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
              if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
                  deltaX = deltaY * -1;
                  deltaY = 0;
              }
  
              // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
              delta = deltaY === 0 ? deltaX : deltaY;
  
              // New school wheel delta (wheel event)
              if ('deltaY' in event) {
                  deltaY = event.deltaY * -1;
                  delta = deltaY;
              }
              if ('deltaX' in event) {
                  deltaX = event.deltaX;
                  if (deltaY === 0) { delta = deltaX * -1; }
              }
  
              // No change actually happened, no reason to go any further
              if (deltaY === 0 && deltaX === 0) { return; }
  
              // Store lowest absolute delta to normalize the delta values
              absDelta = Math.max(Math.abs(deltaY), Math.abs(deltaX));
  
              if (!lowestDelta || absDelta < lowestDelta) {
                  lowestDelta = absDelta;
  
                  // Adjust older deltas if necessary
                  if (shouldAdjustOldDeltas(event, absDelta)) {
                      lowestDelta /= 40;
                  }
              }
  
              // Adjust older deltas if necessary
              if (shouldAdjustOldDeltas(event, absDelta)) {
                  // Divide all the things by 40!
                  delta /= 40;
                  deltaX /= 40;
                  deltaY /= 40;
              }
  
              // Get a whole, normalized value for the deltas
              delta = Math[delta >= 1 ? 'floor' : 'ceil'](delta / lowestDelta);
              deltaX = Math[deltaX >= 1 ? 'floor' : 'ceil'](deltaX / lowestDelta);
              deltaY = Math[deltaY >= 1 ? 'floor' : 'ceil'](deltaY / lowestDelta);
  
              // Normalise offsetX and offsetY properties
              if (this.getBoundingClientRect) {
                  var boundingRect = this.getBoundingClientRect();
                  offsetX = event.clientX - boundingRect.left;
                  offsetY = event.clientY - boundingRect.top;
              }
  
              // Add information to the event object
              event.deltaX = deltaX;
              event.deltaY = deltaY;
              event.deltaFactor = lowestDelta;
              event.offsetX = offsetX;
              event.offsetY = offsetY;
              // Go ahead and set deltaMode to 0 since we converted to pixels
              // Although this is a little odd since we overwrite the deltaX/Y
              // properties with normalized deltas.
              event.deltaMode = 0;
  
              // Add event and delta to the front of the arguments
              args.unshift(event, delta, deltaX, deltaY);
  
              // Clearout lowestDelta after sometime to better
              // handle multiple device types that give different
              // a different lowestDelta
              // Ex: trackpad = 3 and mouse wheel = 120
              if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
              nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
  
  
  
              return fn.apply(this, args);
          }
      };
  
      mousewheelHandler.events = function() {
          var doc = window.document;
          return ( 'onwheel' in doc || doc.documentMode >= 9 ) ?
                 ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
      };
  
      return mousewheelHandler;
  
  }();
  
  
  
  var addListener = function(){
  
      var fn = null,
          prefix = null;
  
      return function addListener(el, event, func) {
  
          if (fn === null) {
              if (el.addEventListener) {
                  fn = "addEventListener";
                  prefix = "";
              }
              else {
                  fn = "attachEvent";
                  prefix = "on";
              }
              //fn = el.attachEvent ? "attachEvent" : "addEventListener";
              //prefix = el.attachEvent ? "on" : "";
          }
  
  
          if (event == "mousewheel") {
              func = mousewheelHandler(func);
              var events = mousewheelHandler.events(),
                  i, l;
              for (i = 0, l = events.length; i < l; i++) {
                  el[fn](prefix + events[i], func, false);
              }
          }
          else {
              el[fn](prefix + event, func, false);
          }
  
          return func;
      }
  
  }();
  
  
  var removeListener = function(){
  
      var fn = null,
          prefix = null;
  
      return function removeListener(el, event, func) {
  
          if (fn === null) {
              if (el.removeEventListener) {
                  fn = "removeEventListener";
                  prefix = "";
              }
              else {
                  fn = "detachEvent";
                  prefix = "on";
              }
              //fn = el.detachEvent ? "detachEvent" : "removeEventListener";
              //prefix = el.detachEvent ? "on" : "";
          }
  
          el[fn](prefix + event, func);
      }
  }();
  
  var isAttached = function(){
      var isAttached = function isAttached(node) {
  
          if (node === window) {
              return true;
          }
          if (node.nodeType == 3) {
              if (node.parentElement) {
                  return isAttached(node.parentElement);
              }
              else {
                  return true;
              }
          }
  
          var html = window.document.documentElement;
  
          return node === html ? true : html.contains(node);
      };
      return isAttached;
  }();
  
  var isAndroid = function(){
  
      var android = null;
  
      return function isAndroid() {
  
          if (android === null) {
              android = parseInt((/android (\d+)/i.exec(navigator.userAgent) || [])[1], 10) || false;
          }
  
          return android;
      };
  
  }();
  
  
  var isIE = function(){
  
      var msie;
  
      return function isIE() {
  
          if (msie === null) {
              var ua = navigator.userAgent;
              msie = parseInt((/msie (\d+)/i.exec(ua) || [])[1], 10);
              if (isNaN(msie)) {
                  msie = parseInt((/trident\/.*; rv:(\d+)/i.exec(ua) || [])[1], 10) || false;
              }
          }
  
          return msie;
      };
  }();
  
  
  
  /**
   * @param {String} event
   * @return {boolean}
   */
  var browserHasEvent = function(){
  
      var eventSupport = {},
          divElm;
  
      return function browserHasEvent(event) {
          // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
          // it. In particular the event is not fired when backspace or delete key are pressed or
          // when cut operation is performed.
  
          if (eventSupport[event] === undf) {
  
              if (event == 'input' && isIE() == 9) {
                  return eventSupport[event] = false;
              }
              if (!divElm) {
                  divElm = window.document.createElement('div');
              }
  
              eventSupport[event] = !!('on' + event in divElm);
          }
  
          return eventSupport[event];
      };
  }();
  
  
  
  /**
   * Modified version of YASS (http://yass.webo.in)
   */
  
  /**
   * Returns array of nodes or an empty array
   * @function select
   * @param {String} selector
   * @param {Element} root to look into
   */
  var select = function() {
  
      var rGeneric    = /^[\w[:#.][\w\]*^|=!]*$/,
          rQuote      = /=([^\]]+)/,
          rGrpSplit   = / *, */,
          rRepPlus    = /(\([^)]*)\+/,
          rRepTild    = /(\[[^\]]+)~/,
          rRepAll     = /(~|>|\+)/,
          rSplitPlus  = / +/,
          rSingleMatch= /([^[:.#]+)?(?:#([^[:.#]+))?(?:\.([^[:.]+))?(?:\[([^!&^*|$[:=]+)([!$^*|&]?=)?([^:\]]+)?\])?(?::([^(]+)(?:\(([^)]+)\))?)?/,
          rNthNum     = /(?:(-?\d*)n)?(?:(%|-)(\d*))?/,
          rNonDig     = /\D/,
          rRepPrnth   = /[^(]*\(([^)]*)\)/,
          rRepAftPrn  = /\(.*/,
          rGetSquare  = /\[([^!~^*|$ [:=]+)([$^*|]?=)?([^ :\]]+)?\]/,
  
          doc         = window.document,
          bcn         = !!doc.getElementsByClassName,
          qsa         = !!doc.querySelectorAll,
  
          /*
           function calls for CSS2/3 modificatos. Specification taken from
           http://www.w3.org/TR/2005/WD-css3-selectors-20051215/
           on success return negative result.
           */
          mods        = {
              /* W3C: "an E element, first child of its parent" */
              'first-child': function (child) {
                  /* implementation was taken from jQuery.1.2.6, line 1394 */
                  return child.parentNode.getElementsByTagName('*')[0] !== child;
              },
              /* W3C: "an E element, last child of its parent" */
              'last-child': function (child) {
                  var brother = child;
                  /* loop in lastChilds while nodeType isn't element */
                  while ((brother = brother.nextSibling) && brother.nodeType !== 1) {}
                  /* Check for node's existence */
                  return !!brother;
              },
              /* W3C: "an E element, root of the document" */
              root: function (child) {
                  return child.nodeName.toLowerCase() !== 'html';
              },
              /* W3C: "an E element, the n-th child of its parent" */
              'nth-child': function (child, ind) {
                  var i = child.nodeIndex || 0,
                      a = ind[3] = ind[3] ? (ind[2] === '%' ? -1 : 1) * ind[3] : 0,
                      b = ind[1];
                  /* check if we have already looked into siblings, using exando - very bad */
                  if (i) {
                      return !( (i + a) % b);
                  } else {
                      /* in the other case just reverse logic for n and loop siblings */
                      var brother = child.parentNode.firstChild;
                      i++;
                      /* looping in child to find if nth expression is correct */
                      do {
                          /* nodeIndex expando used from Peppy / Sizzle/ jQuery */
                          if (brother.nodeType === 1 && (brother.nodeIndex = ++i) && child === brother && ((i + a) % b)) {
                              return 0;
                          }
                      } while (brother = brother.nextSibling);
                      return 1;
                  }
              },
              /*
               W3C: "an E element, the n-th child of its parent,
               counting from the last one"
               */
              'nth-last-child': function (child, ind) {
                  /* almost the same as the previous one */
                  var i = child.nodeIndexLast || 0,
                      a = ind[3] ? (ind[2] === '%' ? -1 : 1) * ind[3] : 0,
                      b = ind[1];
                  if (i) {
                      return !( (i + a) % b);
                  } else {
                      var brother = child.parentNode.lastChild;
                      i++;
                      do {
                          if (brother.nodeType === 1 && (brother.nodeLastIndex = i++) && child === brother && ((i + a) % b)) {
                              return 0;
                          }
                      } while (brother = brother.previousSibling);
                      return 1;
                  }
              },
              /*
               Rrom w3.org: "an E element that has no children (including text nodes)".
               Thx to John, from Sizzle, 2008-12-05, line 416
               */
              empty: function (child) {
                  return !!child.firstChild;
              },
              /* thx to John, stolen from Sizzle, 2008-12-05, line 413 */
              parent: function (child) {
                  return !child.firstChild;
              },
              /* W3C: "an E element, only child of its parent" */
              'only-child': function (child) {
                  return child.parentNode.getElementsByTagName('*').length !== 1;
              },
              /*
               W3C: "a user interface element E which is checked
               (for instance a radio-button or checkbox)"
               */
              checked: function (child) {
                  return !child.checked;
              },
              /*
               W3C: "an element of type E in language "fr"
               (the document language specifies how language is determined)"
               */
              lang: function (child, ind) {
                  return child.lang !== ind && doc.documentElement.lang !== ind;
              },
              /* thx to John, from Sizzle, 2008-12-05, line 398 */
              enabled: function (child) {
                  return child.disabled || child.type === 'hidden';
              },
              /* thx to John, from Sizzle, 2008-12-05, line 401 */
              disabled: function (child) {
                  return !child.disabled;
              },
              /* thx to John, from Sizzle, 2008-12-05, line 407 */
              selected: function(elem){
                  /*
                   Accessing this property makes selected-by-default
                   options in Safari work properly.
                   */
                  var tmp = elem.parentNode.selectedIndex;
                  return !elem.selected;
              }
          },
  
          attrRegCache = {},
  
          getAttrReg  = function(value) {
              return attrRegCache[value] || (attrRegCache[value] = new RegExp('(^| +)' + value + '($| +)'));
          },
  
          attrMods    = {
              /* W3C "an E element with a "attr" attribute" */
              '': function (child, name) {
                  return getAttr(child, name) !== null;
              },
              /*
               W3C "an E element whose "attr" attribute value is
               exactly equal to "value"
               */
              '=': function (child, name, value) {
                  var attrValue;
                  return (attrValue = getAttr(child, name)) && attrValue === value;
              },
              /*
               from w3.prg "an E element whose "attr" attribute value is
               a list of space-separated values, one of which is exactly
               equal to "value"
               */
              '&=': function (child, name, value) {
                  var attrValue;
                  return (attrValue = getAttr(child, name)) && getAttrReg(value).test(attrValue);
              },
              /*
               from w3.prg "an E element whose "attr" attribute value
               begins exactly with the string "value"
               */
              '^=': function (child, name, value) {
                  var attrValue;
                  return (attrValue = getAttr(child, name) + '') && !attrValue.indexOf(value);
              },
              /*
               W3C "an E element whose "attr" attribute value
               ends exactly with the string "value"
               */
              '$=': function (child, name, value) {
                  var attrValue;
                  return (attrValue = getAttr(child, name) + '') &&
                         attrValue.indexOf(value) === attrValue.length - value.length;
              },
              /*
               W3C "an E element whose "attr" attribute value
               contains the substring "value"
               */
              '*=': function (child, name, value) {
                  var attrValue;
                  return (attrValue = getAttr(child, name) + '') && attrValue.indexOf(value) !== -1;
              },
              /*
               W3C "an E element whose "attr" attribute has
               a hyphen-separated list of values beginning (from the
               left) with "value"
               */
              '|=': function (child, name, value) {
                  var attrValue;
                  return (attrValue = getAttr(child, name) + '') &&
                         (attrValue === value || !!attrValue.indexOf(value + '-'));
              },
              /* attr doesn't contain given value */
              '!=': function (child, name, value) {
                  var attrValue;
                  return !(attrValue = getAttr(child, name)) || !getAttrReg(value).test(attrValue);
              }
          };
  
  
      var select = function (selector, root) {
  
          /* clean root with document */
          root = root || doc;
  
          /* sets of nodes, to handle comma-separated selectors */
          var sets    = [],
              qsaErr  = null,
              idx, cls, nodes,
              i, node, ind, mod,
              attrs, attrName, eql, value;
  
          if (qsa && root.querySelectorAll) {
              /* replace not quoted args with quoted one -- Safari doesn't understand either */
              try {
                  sets = toArray(root.querySelectorAll(selector.replace(rQuote, '="$1"')));
              }
              catch (thrownError) {
                  //console.log(thrownError);
                  qsaErr = true;
              }
          }
  
          if (!qsa || qsaErr) {
  
              /* quick return or generic call, missed ~ in attributes selector */
              if (rGeneric.test(selector)) {
  
                  /*
                   some simple cases - only ID or only CLASS for the very first occurence
                   - don't need additional checks. Switch works as a hash.
                   */
                  idx = 0;
  
                  /* the only call -- no cache, thx to GreLI */
                  switch (selector.charAt(0)) {
  
                      case '#':
                          idx = selector.slice(1);
                          sets = doc.getElementById(idx);
  
                          /*
                           workaround with IE bug about returning element by name not by ID.
                           Solution completely changed, thx to deerua.
                           Get all matching elements with this id
                           */
                          if (sets.id !== idx) {
                              sets = doc.all[idx];
                          }
  
                          sets = sets ? [sets] : [];
                          break;
  
                      case '.':
  
                          cls = selector.slice(1);
  
                          if (bcn) {
  
                              sets = toArray((idx = (sets = root.getElementsByClassName(cls)).length) ? sets : []);
  
                          } else {
  
                              /* no RegExp, thx to DenVdmj */
                              cls = ' ' + cls + ' ';
  
                              nodes = root.getElementsByTagName('*');
                              i = 0;
  
                              while (node = nodes[i++]) {
                                  if ((' ' + node.className + ' ').indexOf(cls) !== -1) {
                                      sets[idx++] = node;
                                  }
  
                              }
                              sets = idx ? sets : [];
                          }
                          break;
  
                      case ':':
  
                          nodes   = root.getElementsByTagName('*');
                          i       = 0;
                          ind     = selector.replace(rRepPrnth,"$1");
                          mod     = selector.replace(rRepAftPrn,'');
  
                          while (node = nodes[i++]) {
                              if (mods[mod] && !mods[mod](node, ind)) {
                                  sets[idx++] = node;
                              }
                          }
                          sets = idx ? sets : [];
                          break;
  
                      case '[':
  
                          nodes   = root.getElementsByTagName('*');
                          i       = 0;
                          attrs   = rGetSquare.exec(selector);
                          attrName    = attrs[1];
                          eql     = attrs[2] || '';
                          value   = attrs[3];
  
                          while (node = nodes[i++]) {
                              /* check either attr is defined for given node or it's equal to given value */
                              if (attrMods[eql] && (attrMods[eql](node, attrName, value) ||
                                                    (attrName === 'class' && attrMods[eql](node, 'className', value)))) {
                                  sets[idx++] = node;
                              }
                          }
                          sets = idx ? sets : [];
                          break;
  
                      default:
                          sets = toArray((idx = (sets = root.getElementsByTagName(selector)).length) ? sets : []);
                          break;
                  }
  
              } else {
  
                  /* number of groups to merge or not result arrays */
                  /*
                   groups of selectors separated by commas.
                   Split by RegExp, thx to tenshi.
                   */
                  var groups  = selector.split(rGrpSplit),
                      gl      = groups.length - 1, /* group counter */
                      concat  = !!gl, /* if we need to concat several groups */
                      group,
                      singles,
                      singles_length,
                      single, /* to handle RegExp for single selector */
                      ancestor, /* to remember ancestor call for next childs, default is " " */
                  /* for inner looping */
                      tag, id, klass, newNodes, J, child, last, childs, item, h;
  
                  /* loop in groups, maybe the fastest way */
                  while (group = groups[gl--]) {
  
                      /*
                       Split selectors by space - to form single group tag-id-class,
                       or to get heredity operator. Replace + in child modificators
                       to % to avoid collisions. Additional replace is required for IE.
                       Replace ~ in attributes to & to avoid collisions.
                       */
                      singles_length = (singles = group
                          .replace(rRepPlus,"$1%")
                          .replace(rRepTild,"$1&")
                          .replace(rRepAll," $1 ").split(rSplitPlus)).length;
  
                      i = 0;
                      ancestor = ' ';
                      /* is cleanded up with DOM root */
                      if (root instanceof DocumentFragment) {
                          nodes = root.children;
                      }
                      else {
                          nodes = [root];
                      }
  
                      /*
                       John's Resig fast replace works a bit slower than
                       simple exec. Thx to GreLI for 'greed' RegExp
                       */
                      while (single = singles[i++]) {
  
                          /* simple comparison is faster than hash */
                          if (single !== ' ' && single !== '>' &&
                              single !== '~' && single !== '+' && nodes) {
  
                              single = single.match(rSingleMatch);
  
                              /*
                               Get all required matches from exec:
                               tag, id, class, attribute, value, modificator, index.
                               */
                              tag     = single[1] || '*';
                              id      = single[2];
                              klass   = single[3] ? ' ' + single[3] + ' ' : '';
                              attrName    = single[4];
                              eql     = single[5] || '';
                              mod     = single[7];
  
                              /*
                               for nth-childs modificator already transformed into array.
                               Example used from Sizzle, rev. 2008-12-05, line 362.
                               */
                              ind = mod === 'nth-child' ||
                                      mod === 'nth-last-child' ?
                                    rNthNum.exec(
                                        single[8] === 'even' && '2n' ||
                                        single[8] === 'odd' && '2n%1' ||
                                        !rNonDig.test(single[8]) && '0n%' + single[8] ||
                                        single[8]
                                    ) :
                                    single[8];
  
                              /* new nodes array */
                              newNodes = [];
  
                              /*
                               cached length of new nodes array
                               and length of root nodes
                               */
                              idx = J = 0;
  
                              /* if we need to mark node with expando yeasss */
                              last = i === singles_length;
  
                              /* loop in all root nodes */
                              while (child = nodes[J++]) {
                                  /*
                                   find all TAGs or just return all possible neibours.
                                   Find correct 'children' for given node. They can be
                                   direct childs, neighbours or something else.
                                   */
                                  switch (ancestor) {
                                      case ' ':
                                          if (child.getElementsByTagName) {
                                              childs = child.getElementsByTagName(tag);
                                              h = 0;
                                              while (item = childs[h++]) {
                                                  /*
                                                  check them for ID or Class. Also check for expando 'yeasss'
                                                  to filter non-selected elements. Typeof 'string' not added -
                                                  if we get element with name="id" it won't be equal to given ID string.
                                                  Also check for given attributes selector.
                                                  Modificator is either not set in the selector, or just has been nulled
                                                  by modificator functions hash.
                                                  */
                                                  if ((!id || item.id === id) &&
                                                      (!klass || (' ' + item.className + ' ').indexOf(klass) != -1) &&
                                                      (!attrName || (attrMods[eql] &&
                                                              (attrMods[eql](item, attrName, single[6]) ||
                                                                  (attrName === 'class' &&
                                                                  attrMods[eql](item, 'className', single[6]))))) &&
                                                      !item.yeasss && !(mods[mod] ? mods[mod](item, ind) : mod)) {
  
                                                      /*
                                                      Need to define expando property to true for the last step.
                                                      Then mark selected element with expando
                                                      */
                                                      if (last) {
                                                          item.yeasss = 1;
                                                      }
                                                      newNodes[idx++] = item;
                                                  }
                                              }
                                          }
                                          break;
                                      /* W3C: "an F element preceded by an E element" */
                                      case '~':
  
                                          tag = tag.toLowerCase();
  
                                          /* don't touch already selected elements */
                                          while ((child = child.nextSibling) && !child.yeasss) {
                                              if (child.nodeType === 1 &&
                                                  (tag === '*' || child.nodeName.toLowerCase() === tag) &&
                                                  (!id || child.id === id) &&
                                                  (!klass || (' ' + child.className + ' ').indexOf(klass) !== -1) &&
                                                  (!attrName || (attrMods[eql] &&
                                                             (attrMods[eql](item, attrName, single[6]) ||
                                                              (attrName === 'class' &&
                                                               attrMods[eql](item, 'className', single[6]))))) &&
                                                  !child.yeasss &&
                                                  !(mods[mod] ? mods[mod](child, ind) : mod)) {
  
                                                  if (last) {
                                                      child.yeasss = 1;
                                                  }
                                                  newNodes[idx++] = child;
                                              }
                                          }
                                          break;
  
                                      /* W3C: "an F element immediately preceded by an E element" */
                                      case '+':
                                          while ((child = child.nextSibling) && child.nodeType !== 1) {}
                                          if (child &&
                                              (child.nodeName.toLowerCase() === tag.toLowerCase() || tag === '*') &&
                                              (!id || child.id === id) &&
                                              (!klass || (' ' + item.className + ' ').indexOf(klass) !== -1) &&
                                              (!attrName ||
                                               (attrMods[eql] && (attrMods[eql](item, attrName, single[6]) ||
                                                                  (attrName === 'class' &&
                                                                   attrMods[eql](item, 'className', single[6]))))) &&
                                              !child.yeasss && !(mods[mod] ? mods[mod](child, ind) : mod)) {
  
                                              if (last) {
                                                  child.yeasss = 1;
                                              }
                                              newNodes[idx++] = child;
                                          }
                                          break;
  
                                      /* W3C: "an F element child of an E element" */
                                      case '>':
                                          if (child.getElementsByTagName) {
                                              childs = child.getElementsByTagName(tag);
                                              i = 0;
                                              while (item = childs[i++]) {
                                                  if (item.parentNode === child &&
                                                      (!id || item.id === id) &&
                                                      (!klass || (' ' + item.className + ' ').indexOf(klass) != -1) &&
                                                      (!attrName || (attrMods[eql] &&
                                                              (attrMods[eql](item, attrName, single[6]) ||
                                                                  (attrName === 'class' &&
                                                                  attrMods[eql](item, 'className', single[6]))))) &&
                                                      !item.yeasss &&
                                                      !(mods[mod] ? mods[mod](item, ind) : mod)) {
  
                                                      if (last) {
                                                          item.yeasss = 1;
                                                      }
                                                      newNodes[idx++] = item;
                                                  }
                                              }
                                          }
                                          break;
                                  }
                              }
  
                              /* put selected nodes in local nodes' set */
                              nodes = newNodes;
  
                          } else {
  
                              /* switch ancestor ( , > , ~ , +) */
                              ancestor = single;
                          }
                      }
  
                      if (concat) {
                          /* if sets isn't an array - create new one */
                          if (!nodes.concat) {
                              newNodes = [];
                              h = 0;
                              while (item = nodes[h]) {
                                  newNodes[h++] = item;
                              }
                              nodes = newNodes;
                              /* concat is faster than simple looping */
                          }
                          sets = nodes.concat(sets.length === 1 ? sets[0] : sets);
  
                      } else {
  
                          /* inialize sets with nodes */
                          sets = nodes;
                      }
                  }
  
                  /* define sets length to clean up expando */
                  idx = sets.length;
  
                  /*
                   Need this looping as far as we also have expando 'yeasss'
                   that must be nulled. Need this only to generic case
                   */
                  while (idx--) {
                      sets[idx].yeasss = sets[idx].nodeIndex = sets[idx].nodeIndexLast = null;
                  }
              }
          }
  
          /* return and cache results */
          return sets;
      };
  
      select.is = function(el, selector) {
  
          var els = select(selector, el.parentNode),
              i, l;
  
          for (i = -1, l = els.length; ++i < l;) {
              if (els[i] === el) {
                  return true;
              }
          }
          return false;
      };
  
      return select;
  }();
  
  
  
  
  var Input = function(el, changeFn, changeFnContext, cfg) {
  
      if (el.$$input) {
          if (changeFn) {
              el.$$input.on("change", changeFn, changeFnContext);
          }
          return el.$$input;
      }
  
      var self    = this;
  
      cfg = cfg || {};
  
      self.observable     = new Observable;
      self.el             = el;
      self.inputType      = el.type.toLowerCase();
      self.dataType       = cfg.type || getAttr(el, "data-type") || self.inputType;
      self.listeners      = [];
  
      if (changeFn) {
          self.onChange(changeFn, changeFnContext);
      }
  };
  
  extend(Input.prototype, {
  
      el: null,
      inputType: null,
      dataType: null,
      listeners: null,
      radio: null,
      keydownDelegate: null,
      changeInitialized: false,
  
      destroy: function() {
  
          var self        = this,
              i;
  
          self.observable.destroy();
          self._addOrRemoveListeners(removeListener, true);
  
          self.el.$$input = null;
  
          for (i in self) {
              if (self.hasOwnProperty(i)) {
                  self[i] = null;
              }
          }
      },
  
      _addOrRemoveListeners: function(fn, onlyUsed) {
  
          var self        = this,
              type        = self.inputType,
              listeners   = self.listeners,
              radio       = self.radio,
              el          = self.el,
              used,
              i, ilen,
              j, jlen;
  
          for (i = 0, ilen = listeners.length; i < ilen; i++) {
  
              used = !!listeners[i][2];
  
              if (used === onlyUsed) {
                  if (type === "radio") {
                      for (j = 0, jlen = radio.length; j < jlen; j++) {
                          fn(radio[j], listeners[i][0], listeners[i][1]);
                      }
                  }
                  else {
                      fn(el, listeners[i][0], listeners[i][1]);
                  }
                  listeners[i][2] = !onlyUsed;
              }
          }
      },
  
      initInputChange: function() {
  
          var self = this,
              type = self.inputType;
  
          if (type === "radio") {
              self.initRadioInput();
          }
          else if (type === "checkbox") {
              self.initCheckboxInput();
          }
          else {
              self.initTextInput();
          }
  
          self._addOrRemoveListeners(addListener, false);
  
          self.changeInitialized = true;
      },
  
      initRadioInput: function() {
  
          var self    = this,
              el      = self.el,
              name    = el.name,
              parent;
  
          if (isAttached(el)) {
              parent  = el.ownerDocument;
          }
          else {
              parent = el;
              while (parent.parentNode) {
                  parent = parent.parentNode;
              }
          }
  
          self.radio  = select("input[name="+name+"]", parent);
  
          self.onRadioInputChangeDelegate = bind(self.onRadioInputChange, self);
          self.listeners.push(["click", self.onRadioInputChangeDelegate, false]);
      },
  
      initCheckboxInput: function() {
  
          var self    = this;
  
          self.clicked = false;
  
          self.onCheckboxInputChangeDelegate = bind(self.onCheckboxInputChange, self);
          self.onCheckboxInputClickDelegate = bind(self.onCheckboxInputClick, self);
          self.listeners.push(["click", self.onCheckboxInputClickDelegate, false]);
          self.listeners.push(["change", self.onCheckboxInputChangeDelegate, false]);
      },
  
      initTextInput: function() {
  
          var composing   = false,
              self        = this,
              listeners   = self.listeners,
              timeout;
  
          // In composition mode, users are still inputing intermediate text buffer,
          // hold the listener until composition is done.
          // More about composition events:
          // https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
          if (!isAndroid()) {
  
              var compositionStart    = function() {
                  composing = true;
              };
  
              var compositionEnd  = function() {
                  composing = false;
                  listener();
              };
  
              listeners.push(["compositionstart", compositionStart, false]);
              listeners.push(["compositionend", compositionEnd, false]);
          }
  
          var listener = self.onTextInputChangeDelegate = function(ev) {
              if (composing) {
                  return;
              }
              self.onTextInputChange(ev);
          };
  
          var deferListener = function(ev) {
              if (!timeout) {
                  timeout = setTimeout(function() {
                      listener(ev);
                      timeout = null;
                  }, 0);
              }
          };
  
          var keydown = function(event) {
              event = event || window.event;
              var key = event.keyCode;
  
              // ignore
              //    command            modifiers                   arrows
              if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) {
                  return;
              }
  
              deferListener(event);
          };
  
          // if the browser does support "input" event, we are fine - except on
          // IE9 which doesn't fire the
          // input event on backspace, delete or cut
          if (browserHasEvent('input')) {
  
              listeners.push(["input", listener, false]);
  
          } else {
  
              listeners.push(["keydown", keydown, false]);
  
              // if user modifies input value using context menu in IE,
              // we need "paste" and "cut" events to catch it
              if (browserHasEvent('paste')) {
                  listeners.push(["paste", deferListener, false]);
                  listeners.push(["cut", deferListener, false]);
              }
          }
  
  
          // if user paste into input using mouse on older browser
          // or form autocomplete on newer browser, we need "change" event to catch it
  
          listeners.push(["change", listener, false]);
      },
  
      processValue: function(val) {
  
          switch (this.dataType) {
              case "number":
              case "float":
              case "double":
                  if (val === "" || isNaN(val = parseFloat(val))) {
                      val = undf;
                  }
                  break;
              case "int":
              case "integer":
                  if (val === "" || isNaN(val = parseInt(val, 10))) {
                      val = undf;
                  }
                  break;
              case "bool":
              case "boolean":
                  return !(val === "false" || val === "0" || val === 0 ||
                          val === "off" || val === false || val === "");
  
          }
  
          return val;
      },
  
      onTextInputChange: function(ev) {
  
          var self    = this,
              val     = self.getValue();
  
          self.observable.trigger("change", self.processValue(val));
      },
  
  
      _checkboxChange: function() {
          var self    = this,
              node    = self.el;
  
          self.observable.trigger("change", self.processValue(
              node.checked ? (getAttr(node, "value") || true) : false)
          );
      },
  
      onCheckboxInputChange: function() {
          if (!this.clicked) {
              this._checkboxChange();
          }
          this.clicked = false;
      },
  
      onCheckboxInputClick: function() {
          this._checkboxChange();
          this.clicked = true;
      },
  
      onRadioInputChange: function(e) {
  
          e = e || window.event;
  
          var self    = this,
              trg     = e.target || e.srcElement;
  
          self.observable.trigger("change", self.processValue(trg.value));
      },
  
      setValue: function(val) {
  
          var self    = this,
              type    = self.inputType,
              radio,
              i, len;
  
          val = self.processValue(val);
  
          if (type === "radio") {
  
              radio = self.radio;
  
              for (i = 0, len = radio.length; i < len; i++) {
                  radio[i].checked = self.processValue(radio[i].value) == val;
              }
          }
          else if (type === "checkbox") {
              var node        = self.el;
              node.checked    = val === true || val == self.processValue(node.value);
          }
          else {
  
              if (val === undf) {
                  val = "";
              }
  
              setValue(self.el, val);
          }
  
          self.triggerChange();
      },
  
      getValue: function() {
  
          var self    = this,
              type    = self.inputType,
              radio,
              i, l;
  
          if (type === "radio") {
              radio = self.radio;
              for (i = 0, l = radio.length; i < l; i++) {
                  if (radio[i].checked) {
                      return self.processValue(radio[i].value);
                  }
              }
              return null;
          }
          else if (type === "checkbox") {
              return self.processValue(self.el.checked ? (getAttr(self.el, "value") || true) : false);
          }
          else {
              return self.processValue(getValue(self.el));
          }
      },
  
  
      onChange: function(fn, context) {
          var self = this;
          if (!self.changeInitialized) {
              self.initInputChange();
          }
          this.observable.on("change", fn, context);
      },
  
      unChange: function(fn, context) {
          this.observable.un("change", fn, context);
      },
  
  
      onKey: function(key, fn, context, args) {
  
          var self = this;
  
          if (!self.keydownDelegate) {
              self.keydownDelegate = bind(self.keyHandler, self);
              self.listeners.push(["keydown", self.keydownDelegate, false]);
              addListener(self.el, "keydown", self.keydownDelegate);
              self.observable.createEvent("key", {
                  returnResult: false,
                  triggerFilter: self.keyEventFilter
              });
          }
  
          self.observable.on("key", fn, context, {
              key: key,
              prepend: args
          });
      },
  
      unKey: function(key, fn, context) {
  
          var self    = this;
          self.observable.un("key", fn, context);
      },
  
      keyEventFilter: function(l, args) {
  
          var key = l.key,
              e = args[0];
  
          if (typeof key !== "object") {
              return key === e.keyCode;
          }
          else {
              if (key.ctrlKey !== undf && key.ctrlKey !== e.ctrlKey) {
                  return false;
              }
              if (key.shiftKey !== undf && key.shiftKey !== e.shiftKey) {
                  return false;
              }
              return !(key.keyCode !== undf && key.keyCode !== e.keyCode);
          }
      },
  
      keyHandler: function(event) {
  
          var e       = normalizeEvent(event || window.event),
              self    = this;
  
          self.observable.trigger("key", e);
      },
  
      triggerChange: function() {
          if ("createEvent" in document) {
              var evt = document.createEvent("HTMLEvents");
              evt.initEvent("change", false, true);
              this.el.dispatchEvent(evt);
          }
          else {
              this.el.fireEvent("onchange");
          }
      }
  
  
  }, true, false);
  
  
  Input.get = function(node, scope) {
      if (node.$$input) {
          return node.$$input;
      }
      if (scope && !node.type) {
          var cmp = scope.$app.getParentCmp(node, true);
          if (cmp && cmp.getInputInterface) {
              return cmp.getInputInterface();
          }
      }
      return new Input(node);
  };
  
  Input.getValue = getValue;
  Input.setValue = setValue;
  
  
  
  
  
  
  
  
  Directive.registerAttribute("bind", 1000, Directive.$extend({
  
      $class: "Directive.attr.Bind",
      isInput: false,
      input: null,
      lockInput: null,
      recursive: false,
      textRenderer: null,
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self    = this,
              cfg     = attr ? attr.config : {};
  
          self.isInput    = isField(node);
          self.recursive  = !!cfg.recursive;
          self.locked     = !!cfg.locked;
  
          if (self.isInput) {
              //self.input  = new Input(node, self.onInputChange, self);
              self.input = Input.get(node);
              self.input.onChange(self.onInputChange, self);
          }
  
          if (self.recursive) {
              self.scope  = scope;
              self.node   = node;
              self.textRenderer = new TextRenderer(scope, '{{' + expr + '}}', {recursive: true});
              self.textRenderer.subscribe(self.onTextRendererChange, self);
              self.onTextRendererChange();
  
              if (scope instanceof Scope) {
                  scope.$on("destroy", self.onScopeDestroy, self);
              }
          }
          else {
              self.$super(scope, node, expr);
          }
      },
  
      onInputChange: function(val) {
  
          var self = this;
          if (self.locked && val != self.watcher.getLastResult()) {
              self.onChange();
          }
      },
  
      onTextRendererChange: function() {
          var self    = this;
          self.updateElement(self.textRenderer.getString());
      },
  
      onChange: function() {
          var self    = this,
              val     = self.watcher.getLastResult();
  
          self.updateElement(val);
      },
  
      updateElement: function(val) {
  
          var self = this;
  
          if (self.isInput) {
              self.input.setValue(val);
          }
          else {
              self.node[typeof self.node.textContent === "string" ? "textContent" : "innerText"] = val;
          }
      },
  
      destroy: function() {
  
          var self    = this;
  
          if (self.textRenderer) {
              self.textRenderer.$destroy();
              self.textRenderer = null;
          }
  
          if (self.input) {
              self.input.destroy();
              self.input = null;
          }
  
          self.$super();
      }
  }));
  
  
  
  
  
  
  Directive.registerAttribute("bind-html", 1000, defineClass({
  
      $class: "Directive.attr.BindHtml",
      $extends: "Directive.attr.Bind",
  
      updateElement: function(val) {
          this.node.innerHTML = val;
      }
  }));
  
  
  
  var getAnimationPrefixes = function(){
  
      var domPrefixes         = ['Moz', 'Webkit', 'ms', 'O', 'Khtml'],
          animationDelay      = "animationDelay",
          animationDuration   = "animationDuration",
          transitionDelay     = "transitionDelay",
          transitionDuration  = "transitionDuration",
          transform           = "transform",
          transitionend       = null,
          prefixes            = null,
  
          probed              = false,
  
          detectCssPrefixes   = function() {
  
              var el = window.document.createElement("div"),
                  animation = false,
                  pfx,
                  i, len;
  
              if (el.style['animationName'] !== undf) {
                  animation = true;
              }
              else {
                  for(i = 0, len = domPrefixes.length; i < len; i++) {
                      pfx = domPrefixes[i];
                      if (el.style[ pfx + 'AnimationName' ] !== undf) {
                          animation           = true;
                          animationDelay      = pfx + "AnimationDelay";
                          animationDuration   = pfx + "AnimationDuration";
                          transitionDelay     = pfx + "TransitionDelay";
                          transitionDuration  = pfx + "TransitionDuration";
                          transform           = pfx + "Transform";
                          break;
                      }
                  }
              }
  
              if (animation) {
                  if('ontransitionend' in window) {
                      // Chrome/Saf (+ Mobile Saf)/Android
                      transitionend = 'transitionend';
                  }
                  else if('onwebkittransitionend' in window) {
                      // Chrome/Saf (+ Mobile Saf)/Android
                      transitionend = 'webkitTransitionEnd';
                  }
              }
  
              return animation;
          };
  
  
      /**
       * @function animate.getPrefixes
       * @returns {object}
       */
      return function() {
  
          if (!probed) {
              if (detectCssPrefixes()) {
                  prefixes = {
                      animationDelay: animationDelay,
                      animationDuration: animationDuration,
                      transitionDelay: transitionDelay,
                      transitionDuration: transitionDuration,
                      transform: transform,
                      transitionend: transitionend
                  };
              }
              else {
                  prefixes = {};
              }
  
              probed = true;
          }
  
  
          return prefixes;
      };
  }();
  
  
  
  var getAnimationDuration = function(){
  
      var parseTime       = function(str) {
              if (!str) {
                  return 0;
              }
              var time = parseFloat(str);
              if (str.indexOf("ms") === -1) {
                  time *= 1000;
              }
              return time;
          },
  
          getMaxTimeFromPair = function(max, dur, delay) {
  
              var i, sum, len = dur.length;
  
              for (i = 0; i < len; i++) {
                  sum = parseTime(dur[i]) + parseTime(delay[i]);
                  max = Math.max(sum, max);
              }
  
              return max;
          },
  
          pfx                 = false,
          animationDuration   = null,
          animationDelay      = null,
          transitionDuration  = null,
          transitionDelay     = null;
  
  
      /**
       * @function animate.getDuration
       * @param {Element} el
       * @returns {number}
       */
      return function(el) {
  
          if (pfx === false) {
              pfx = getAnimationPrefixes();
              animationDuration = pfx ? pfx.animationDuration : null;
              animationDelay = pfx ? pfx.animationDelay : null;
              transitionDuration = pfx ? pfx.transitionDuration : null;
              transitionDelay = pfx ? pfx.transitionDelay : null;
          }
  
          if (!pfx) {
              return 0;
          }
  
          var style       = window.getComputedStyle ? window.getComputedStyle(el, null) : el.style,
              duration    = 0,
              animDur     = (style[animationDuration] || '').split(','),
              animDelay   = (style[animationDelay] || '').split(','),
              transDur    = (style[transitionDuration] || '').split(','),
              transDelay  = (style[transitionDelay] || '').split(',');
  
          duration    = Math.max(duration, getMaxTimeFromPair(duration, animDur, animDelay));
          duration    = Math.max(duration, getMaxTimeFromPair(duration, transDur, transDelay));
  
          return duration;
      };
  
  }();
  
  
  
  
  
  
  var data = function(){
  
      var dataCache   = {},
  
          getNodeId   = function(el) {
              return el._mjsid || (el._mjsid = nextUid());
          };
  
      /**
       * @param {Element} el
       * @param {String} key
       * @param {*} value optional
       */
      return function data(el, key, value) {
          var id  = getNodeId(el),
              obj = dataCache[id];
  
          if (value !== undf) {
              if (!obj) {
                  obj = dataCache[id] = {};
              }
              obj[key] = value;
              return value;
          }
          else {
              return obj ? obj[key] : undf;
          }
      };
  
  }();
  
  var getRegExp = function(){
  
      var cache = {};
  
      /**
       * @param {String} expr
       * @returns RegExp
       */
      return function getRegExp(expr) {
          return cache[expr] || (cache[expr] = new RegExp(expr));
      };
  }();
  
  
  
  /**
   * @param {String} cls
   * @returns {RegExp}
   */
  function getClsReg(cls) {
      return getRegExp('(?:^|\\s)'+cls+'(?!\\S)');
  };
  
  
  
  /**
   * @param {Element} el
   * @param {String} cls
   */
  function removeClass(el, cls) {
      if (cls) {
          el.className = el.className.replace(getClsReg(cls), '');
      }
  };
  
  
  
  /**
   * @function animate.stop
   * @param {Element} el
   */
  var stopAnimation = function(el) {
  
      var queue = data(el, "mjsAnimationQueue"),
          current,
          position,
          stages;
  
      if (isArray(queue) && queue.length) {
          current = queue[0];
  
          if (current) {
              if (current.stages) {
                  position = current.position;
                  stages = current.stages;
                  removeClass(el, stages[position]);
                  removeClass(el, stages[position] + "-active");
              }
              if (current.deferred) {
                  current.deferred.reject(current.el);
              }
          }
      }
      else if (isFunction(queue)) {
          queue(el);
      }
      else if (queue === "stop") {
          $(el).stop(true, true);
      }
  
      data(el, "mjsAnimationQueue", null);
  };
  
  
  
  
  /**
   * Returns 'then' function or false
   * @param {*} any
   * @returns {Function|boolean}
   */
  function isThenable(any) {
  
      // any.then must only be accessed once
      // this is a promise/a+ requirement
  
      if (!any) { //  || !any.then
          return false;
      }
      var then, t;
  
      //if (!any || (!isObject(any) && !isFunction(any))) {
      if (((t = typeof any) != "object" && t != "function")) {
          return false;
      }
      return isFunction((then = any.then)) ?
             then : false;
  };
  
  
  
  
  var Promise = function(){
  
      var PENDING     = 0,
          FULFILLED   = 1,
          REJECTED    = 2,
  
          queue       = [],
          qRunning    = false,
  
  
          nextTick    = typeof process !== strUndef ?
                          process.nextTick :
                          function(fn) {
                              setTimeout(fn, 0);
                          },
  
          // synchronous queue of asynchronous functions:
          // callbacks must be called in "platform stack"
          // which means setTimeout/nextTick;
          // also, they must be called in a strict order.
          nextInQueue = function() {
              qRunning    = true;
              var next    = queue.shift();
              nextTick(function(){
                  next[0].apply(next[1], next[2]);
                  if (queue.length) {
                      nextInQueue();
                  }
                  else {
                      qRunning = false;
                  }
              }, 0);
          },
  
          /**
           * add to execution queue
           * @param {Function} fn
           * @param {Object} scope
           * @param {[]} args
           * @ignore
           */
          next        = function(fn, scope, args) {
              args = args || [];
              queue.push([fn, scope, args]);
              if (!qRunning) {
                  nextInQueue();
              }
          },
  
          /**
           * returns function which receives value from previous promise
           * and tries to resolve next promise with new value returned from given function(prev value)
           * or reject on error.
           * promise1.then(success, failure) -> promise2
           * wrapper(success, promise2) -> fn
           * fn(promise1 resolve value) -> new value
           * promise2.resolve(new value)
           *
           * @param {Function} fn
           * @param {Promise} promise
           * @returns {Function}
           * @ignore
           */
          wrapper     = function(fn, promise) {
              return function(value) {
                  try {
                      promise.resolve(fn(value));
                  }
                  catch (thrownError) {
                      promise.reject(thrownError);
                  }
              };
          };
  
  
      /**
       * @class Promise
       */
  
  
      /**
       * @method Promise
       * @param {Function} fn {
       *  @description Function that accepts two parameters: resolve and reject functions.
       *  @param {function} resolve {
       *      @param {*} value
       *  }
       *  @param {function} reject {
       *      @param {*} reason
       *  }
       * }
       * @param {Object} context
       * @returns {Promise}
       * @constructor
       */
  
      /**
       * @method Promise
       * @param {Thenable} thenable
       * @returns {Promise}
       * @constructor
       */
  
      /**
       * @method Promise
       * @param {*} value Value to resolve promise with
       * @returns {Promise}
       * @constructor
       */
  
  
      /**
       * @method Promise
       * @returns {Promise}
       * @constructor
       */
      var Promise = function(fn, context) {
  
          if (fn instanceof Promise) {
              return fn;
          }
  
          if (!(this instanceof Promise)) {
              return new Promise(fn, context);
          }
  
          var self = this,
              then;
  
          self._fulfills   = [];
          self._rejects    = [];
          self._dones      = [];
          self._fails      = [];
  
          if (arguments.length > 0) {
  
              if (then = isThenable(fn)) {
                  if (fn instanceof Promise) {
                      fn.then(
                          bind(self.resolve, self),
                          bind(self.reject, self));
                  }
                  else {
                      (new Promise(then, fn)).then(
                          bind(self.resolve, self),
                          bind(self.reject, self));
                  }
              }
              else if (isFunction(fn)) {
                  try {
                      fn.call(context,
                              bind(self.resolve, self),
                              bind(self.reject, self));
                  }
                  catch (thrownError) {
                      self.reject(thrownError);
                  }
              }
              else {
                  self.resolve(fn);
              }
          }
      };
  
      extend(Promise.prototype, {
  
          _state: PENDING,
  
          _fulfills: null,
          _rejects: null,
          _dones: null,
          _fails: null,
  
          _wait: 0,
  
          _value: null,
          _reason: null,
  
          _triggered: false,
  
          isPending: function() {
              return this._state === PENDING;
          },
  
          isFulfilled: function() {
              return this._state === FULFILLED;
          },
  
          isResolved: function() {
              return this._state === FULFILLED;
          },
  
          isRejected: function() {
              return this._state === REJECTED;
          },
  
          hasListeners: function() {
              var self = this,
                  ls  = [self._fulfills, self._rejects, self._dones, self._fails],
                  i, l;
  
              for (i = 0, l = ls.length; i < l; i++) {
                  if (ls[i] && ls[i].length) {
                      return true;
                  }
              }
  
              return false;
          },
  
          _cleanup: function() {
              var self    = this;
  
              self._fulfills = null;
              self._rejects = null;
              self._dones = null;
              self._fails = null;
          },
  
          _processValue: function(value, cb) {
  
              var self    = this,
                  then;
  
              if (self._state !== PENDING) {
                  return;
              }
  
              if (value === self) {
                  self._doReject(new TypeError("cannot resolve promise with itself"));
                  return;
              }
  
              try {
                  if (then = isThenable(value)) {
                      if (value instanceof Promise) {
                          value.then(
                              bind(self._processResolveValue, self),
                              bind(self._processRejectReason, self));
                      }
                      else {
                          (new Promise(then, value)).then(
                              bind(self._processResolveValue, self),
                              bind(self._processRejectReason, self));
                      }
                      return;
                  }
              }
              catch (thrownError) {
                  if (self._state === PENDING) {
                      self._doReject(thrownError);
                  }
                  return;
              }
  
              cb.call(self, value);
          },
  
  
          _callResolveHandlers: function() {
  
              var self    = this;
  
              self._done();
  
              var cbs  = self._fulfills,
                  cb;
  
              while (cb = cbs.shift()) {
                  next(cb[0], cb[1], [self._value]);
              }
  
              self._cleanup();
          },
  
  
          _doResolve: function(value) {
              var self    = this;
  
              self._value = value;
              self._state = FULFILLED;
  
              if (self._wait === 0) {
                  self._callResolveHandlers();
              }
          },
  
          _processResolveValue: function(value) {
              this._processValue(value, this._doResolve);
          },
  
          /**
           * @param {*} value
           */
          resolve: function(value) {
  
              var self    = this;
  
              if (self._triggered) {
                  return self;
              }
  
              self._triggered = true;
              self._processResolveValue(value);
  
              return self;
          },
  
  
          _callRejectHandlers: function() {
  
              var self    = this;
  
              self._fail();
  
              var cbs  = self._rejects,
                  cb;
  
              while (cb = cbs.shift()) {
                  next(cb[0], cb[1], [self._reason]);
              }
  
              self._cleanup();
          },
  
          _doReject: function(reason) {
  
              var self        = this;
  
              self._state     = REJECTED;
              self._reason    = reason;
  
              if (self._wait === 0) {
                  self._callRejectHandlers();
              }
          },
  
  
          _processRejectReason: function(reason) {
              this._processValue(reason, this._doReject);
          },
  
          /**
           * @param {*} reason
           */
          reject: function(reason) {
  
              var self    = this;
  
              if (self._triggered) {
                  return self;
              }
  
              self._triggered = true;
  
              self._processRejectReason(reason);
  
              return self;
          },
  
          /**
           * @param {Function} resolve -- called when this promise is resolved; returns new resolve value
           * @param {Function} reject -- called when this promise is rejects; returns new reject reason
           * @param {object} context -- resolve's and reject's functions "this" object
           * @returns {Promise} new promise
           */
          then: function(resolve, reject, context) {
  
              var self            = this,
                  promise         = new Promise,
                  state           = self._state;
  
              if (context) {
                  if (resolve) {
                      resolve = bind(resolve, context);
                  }
                  if (reject) {
                      reject = bind(reject, context);
                  }
              }
  
              if (state === PENDING || self._wait !== 0) {
  
                  if (resolve && isFunction(resolve)) {
                      self._fulfills.push([wrapper(resolve, promise), null]);
                  }
                  else {
                      self._fulfills.push([promise.resolve, promise])
                  }
  
                  if (reject && isFunction(reject)) {
                      self._rejects.push([wrapper(reject, promise), null]);
                  }
                  else {
                      self._rejects.push([promise.reject, promise]);
                  }
              }
              else if (state === FULFILLED) {
  
                  if (resolve && isFunction(resolve)) {
                      next(wrapper(resolve, promise), null, [self._value]);
                  }
                  else {
                      promise.resolve(self._value);
                  }
              }
              else if (state === REJECTED) {
                  if (reject && isFunction(reject)) {
                      next(wrapper(reject, promise), null, [self._reason]);
                  }
                  else {
                      promise.reject(self._reason);
                  }
              }
  
              return promise;
          },
  
          /**
           * @param {Function} reject -- same as then(null, reject)
           * @returns {Promise} new promise
           */
          "catch": function(reject) {
              return this.then(null, reject);
          },
  
          _done: function() {
  
              var self    = this,
                  cbs     = self._dones,
                  cb;
  
              while (cb = cbs.shift()) {
                  try {
                      cb[0].call(cb[1] || null, self._value);
                  }
                  catch (thrown) {
                      error(thrown);
                  }
              }
          },
  
          /**
           * @param {Function} fn -- function to call when promise is resolved
           * @param {Object} context -- function's "this" object
           * @returns {Promise} same promise
           */
          done: function(fn, context) {
              var self    = this,
                  state   = self._state;
  
              if (state === FULFILLED && self._wait === 0) {
                  try {
                      fn.call(context || null, self._value);
                  }
                  catch (thrown) {
                      error(thrown);
                  }
              }
              else if (state === PENDING) {
                  self._dones.push([fn, context]);
              }
  
              return self;
          },
  
          _fail: function() {
  
              var self    = this,
                  cbs     = self._fails,
                  cb;
  
              while (cb = cbs.shift()) {
                  try {
                      cb[0].call(cb[1] || null, self._reason);
                  }
                  catch (thrown) {
                      error(thrown);
                  }
              }
          },
  
          /**
           * @param {Function} fn -- function to call when promise is rejected.
           * @param {Object} context -- function's "this" object
           * @returns {Promise} same promise
           */
          fail: function(fn, context) {
  
              var self    = this,
                  state   = self._state;
  
              if (state === REJECTED && self._wait === 0) {
                  try {
                      fn.call(context || null, self._reason);
                  }
                  catch (thrown) {
                      error(thrown);
                  }
              }
              else if (state === PENDING) {
                  self._fails.push([fn, context]);
              }
  
              return self;
          },
  
          /**
           * @param {Function} fn -- function to call when promise resolved or rejected
           * @param {Object} context -- function's "this" object
           * @return {Promise} same promise
           */
          always: function(fn, context) {
              this.done(fn, context);
              this.fail(fn, context);
              return this;
          },
  
          /**
           * @returns {object} then: function, done: function, fail: function, always: function
           */
          promise: function() {
              var self = this;
              return {
                  then: bind(self.then, self),
                  done: bind(self.done, self),
                  fail: bind(self.fail, self),
                  always: bind(self.always, self)
              };
          },
  
          after: function(value) {
  
              var self = this;
  
              if (isThenable(value)) {
  
                  self._wait++;
  
                  var done = function() {
                      self._wait--;
                      if (self._wait === 0 && self._state !== PENDING) {
                          self._state === FULFILLED ?
                              self._callResolveHandlers() :
                              self._callRejectHandlers();
                      }
                  };
  
                  if (isFunction(value.done)) {
                      value.done(done);
                  }
                  else {
                      value.then(done);
                  }
              }
  
              return self;
          }
      }, true, false);
  
  
      /**
       * @param {function} fn
       * @param {object} context
       * @param {[]} args
       * @returns {Promise}
       * @static
       */
      Promise.fcall = function(fn, context, args) {
          return Promise.resolve(fn.apply(context, args || []));
      };
  
      /**
       * @param {*} value
       * @returns {Promise}
       * @static
       */
      Promise.resolve = function(value) {
          var p = new Promise;
          p.resolve(value);
          return p;
      };
  
  
      /**
       * @param {*} reason
       * @returns {Promise}
       * @static
       */
      Promise.reject = function(reason) {
          var p = new Promise;
          p.reject(reason);
          return p;
      };
  
  
      /**
       * @param {[]} promises -- array of promises or resolve values
       * @returns {Promise}
       * @static
       */
      Promise.all = function(promises) {
  
          if (!promises.length) {
              return Promise.resolve(null);
          }
  
          var p       = new Promise,
              len     = promises.length,
              values  = new Array(len),
              cnt     = len,
              i,
              item,
              done    = function(value, inx) {
                  values[inx] = value;
                  cnt--;
  
                  if (cnt === 0) {
                      p.resolve(values);
                  }
              };
  
          for (i = 0; i < len; i++) {
  
              (function(inx){
                  item = promises[i];
  
                  if (item instanceof Promise) {
                      item.done(function(value){
                          done(value, inx);
                      })
                          .fail(p.reject, p);
                  }
                  else if (isThenable(item) || isFunction(item)) {
                      (new Promise(item))
                          .done(function(value){
                              done(value, inx);
                          })
                          .fail(p.reject, p);
                  }
                  else {
                      done(item, inx);
                  }
              })(i);
          }
  
          return p;
      };
  
      /**
       * @param {Promise|*} promise1
       * @param {Promise|*} promise2
       * @param {Promise|*} promiseN
       * @returns {Promise}
       * @static
       */
      Promise.when = function() {
          return Promise.all(arguments);
      };
  
      /**
       * @param {[]} promises -- array of promises or resolve values
       * @returns {Promise}
       * @static
       */
      Promise.allResolved = function(promises) {
  
          if (!promises.length) {
              return Promise.resolve(null);
          }
  
          var p       = new Promise,
              len     = promises.length,
              values  = [],
              cnt     = len,
              i,
              item,
              settle  = function(value) {
                  values.push(value);
                  proceed();
              },
              proceed = function() {
                  cnt--;
                  if (cnt === 0) {
                      p.resolve(values);
                  }
              };
  
          for (i = 0; i < len; i++) {
              item = promises[i];
  
              if (item instanceof Promise) {
                  item.done(settle).fail(proceed);
              }
              else if (isThenable(item) || isFunction(item)) {
                  (new Promise(item)).done(settle).fail(proceed);
              }
              else {
                  settle(item);
              }
          }
  
          return p;
      };
  
      /**
       * @param {[]} promises -- array of promises or resolve values
       * @returns {Promise}
       * @static
       */
      Promise.race = function(promises) {
  
          if (!promises.length) {
              return Promise.resolve(null);
          }
  
          var p   = new Promise,
              len = promises.length,
              i,
              item;
  
          for (i = 0; i < len; i++) {
              item = promises[i];
  
              if (item instanceof Promise) {
                  item.done(p.resolve, p).fail(p.reject, p);
              }
              else if (isThenable(item) || isFunction(item)) {
                  (new Promise(item)).done(p.resolve, p).fail(p.reject, p);
              }
              else {
                  p.resolve(item);
              }
  
              if (!p.isPending()) {
                  break;
              }
          }
  
          return p;
      };
  
      /**
       * @param {[]} functions -- array of promises or resolve values or functions
       * @returns {Promise}
       * @static
       */
      Promise.waterfall = function(functions) {
  
          if (!functions.length) {
              return Promise.resolve(null);
          }
  
          var first   = functions.shift(),
              promise = isFunction(first) ? Promise.fcall(first) : Promise.resolve(fn),
              fn;
  
          while (fn = functions.shift()) {
              if (isThenable(fn)) {
                  promise = promise.then(function(fn){
                      return function(){
                          return fn;
                      };
                  }(fn));
              }
              else if (isFunction(fn)) {
                  promise = promise.then(fn);
              }
              else {
                  promise.resolve(fn);
              }
          }
  
          return promise;
      };
  
      Promise.forEach = function(items, fn, context, allResolved) {
  
          var left = items.slice(),
              p = new Promise,
              values = [],
              i = 0;
  
          var next = function() {
  
              if (!left.length) {
                  p.resolve(values);
                  return;
              }
  
              var item = left.shift(),
                  index = i;
  
              i++;
  
              Promise.fcall(fn, context, [item, index])
                  .done(function(result){
                      values.push(result);
                      next();
                  })
                  .fail(function(reason){
                      if (allResolved) {
                          p.reject(reason);
                      }
                      else {
                          values.push(null);
                          next();
                      }
                  });
          };
  
          next();
  
          return p;
      };
  
      Promise.counter = function(cnt) {
  
          var promise     = new Promise;
  
          promise.countdown = function() {
              cnt--;
              if (cnt === 0) {
                  promise.resolve();
              }
          };
  
          return promise;
      };
  
      return Promise;
  }();
  
  
  
  
  
  /**
   * @param {Element} el
   * @param {String} cls
   * @returns {boolean}
   */
  function hasClass(el, cls) {
      return cls ? getClsReg(cls).test(el.className) : false;
  };
  
  
  
  /**
   * @param {Element} el
   * @param {String} cls
   */
  function addClass(el, cls) {
      if (cls && !hasClass(el, cls)) {
          el.className += " " + cls;
      }
  };
  
  
  
  var raf = function() {
  
      var raf,
          cancel;
  
      if (typeof window !== strUndef) {
          var w   = window;
          raf     = w.requestAnimationFrame ||
                      w.webkitRequestAnimationFrame ||
                      w.mozRequestAnimationFrame;
          cancel  = w.cancelAnimationFrame ||
                      w.webkitCancelAnimationFrame ||
                      w.mozCancelAnimationFrame ||
                      w.webkitCancelRequestAnimationFrame;
  
          if (raf) {
              return function(fn, context, args) {
                  var id = raf(context || args ? function(){
                      fn.apply(context, args || []);
                  } : fn);
                  return function() {
                      cancel(id);
                  };
              };
          }
      }
  
      return function(fn, context, args){
          var id = async(fn, context, args, 0);
          return function(){
              clearTimeout(id);
          };
      };
  
  }();
  
  
  
  
  var animate = function(){
  
  
      var types           = {
              "show":     ["mjs-show"],
              "hide":     ["mjs-hide"],
              "enter":    ["mjs-enter"],
              "leave":    ["mjs-leave"],
              "move":     ["mjs-move"]
          },
  
          animId          = 0,
  
          prefixes        = false,
          cssAnimations   = false,
  
          dataParam       = "mjsAnimationQueue",
  
          callTimeout     = function(fn, startTime, duration) {
              var tick = function(){
                  var time = (new Date).getTime();
                  if (time - startTime >= duration) {
                      fn();
                  }
                  else {
                      raf(tick);
                  }
              };
              raf(tick);
          },
  
  
          cssAnimSupported= function(){
              if (prefixes === false) {
                  prefixes        = getAnimationPrefixes();
                  cssAnimations   = !!prefixes;
              }
              return cssAnimations;
          },
  
  
  
          nextInQueue     = function(el) {
              var queue = data(el, dataParam),
                  next;
              if (queue.length) {
                  next = queue[0];
                  animationStage(next.el, next.stages, 0, next.start, next.deferred, false, next.id, next.step);
              }
              else {
                  data(el, dataParam, null);
              }
          },
  
          animationStage  = function animationStage(el, stages, position, startCallback,
                                                    deferred, first, id, stepCallback) {
  
              var stopped   = function() {
                  var q = data(el, dataParam);
                  if (!q || !q.length || q[0].id != id) {
                      deferred.reject(el);
                      return true;
                  }
                  return false;
              };
  
              var finishStage = function() {
  
                  if (stopped()) {
                      return;
                  }
  
                  var thisPosition = position;
  
                  position++;
  
                  if (position === stages.length) {
                      deferred.resolve(el);
                      data(el, dataParam).shift();
                      nextInQueue(el);
                  }
                  else {
                      data(el, dataParam)[0].position = position;
                      animationStage(el, stages, position, null, deferred, false, id, stepCallback);
                  }
  
                  removeClass(el, stages[thisPosition]);
                  removeClass(el, stages[thisPosition] + "-active");
              };
  
              var setStage = function() {
  
                  if (!stopped()) {
  
                      addClass(el, stages[position] + "-active");
  
                      Promise.resolve(stepCallback && stepCallback(el, position, "active"))
                          .done(function(){
                              if (!stopped()) {
  
                                  var duration = getAnimationDuration(el);
  
                                  if (duration) {
                                      callTimeout(finishStage, (new Date).getTime(), duration);
                                  }
                                  else {
                                      raf(finishStage);
                                  }
                              }
                          });
                  }
  
              };
  
              var start = function(){
  
                  if (!stopped()) {
                      addClass(el, stages[position]);
  
                      Promise.waterfall([
                              stepCallback && stepCallback(el, position, "start"),
                              function(){
                                  return startCallback ? startCallback(el) : null;
                              }
                          ])
                          .done(function(){
                              !stopped() && raf(setStage);
                          });
                  }
              };
  
              first ? raf(start) : start();
          };
  
  
      /**
       * @function animate
       * @param {Element} el Element being animated
       * @param {string|function|[]|object} animation {
       *  'string' - registered animation name,<br>
       *  'function' - fn(el, callback) - your own animation<br>
       *  'array' - array or stages (class names)<br>
       *  'array' - [{before}, {after}] - jquery animation<br>
       *  'object' - {stages, fn, before, after, options, context, duration, start}
       * }
       * @param {function} startCallback call this function before animation begins
       * @param {function} stepCallback call this function between stages
       * @returns {MetaphorJs.Promise}
       */
      var animate = function animate(el, animation, startCallback, stepCallback) {
  
          var deferred    = new Promise,
              queue       = data(el, dataParam) || [],
              id          = ++animId,
              stages,
              jsFn,
              before, after,
              options, context,
              duration;
  
          if (animation) {
  
              if (isString(animation)) {
                  stages = types[animation];
              }
              else if (isFunction(animation)) {
                  jsFn = animation;
              }
              else if (isArray(animation)) {
                  if (isString(animation[0])) {
                      stages = animation;
                  }
                  else {
                      before = animation[0];
                      after = animation[1];
                  }
              }
  
              if (isPlainObject(animation)) {
                  stages      = animation.stages;
                  jsFn        = animation.fn;
                  before      = animation.before;
                  after       = animation.after;
                  options     = animation.options ? extend({}, animation.options) : {};
                  context     = animation.context || null;
                  duration    = animation.duration || null;
                  startCallback   = startCallback || options.start;
              }
  
  
              if (cssAnimSupported() && stages) {
  
                  queue.push({
                      el: el,
                      stages: stages,
                      start: startCallback,
                      step: stepCallback,
                      deferred: deferred,
                      position: 0,
                      id: id
                  });
                  data(el, dataParam, queue);
  
                  if (queue.length === 1) {
                      animationStage(el, stages, 0, startCallback, deferred, true, id, stepCallback);
                  }
  
                  return deferred;
              }
              else {
  
                  options = options || {};
  
                  startCallback && (options.start = function(){
                      startCallback(el);
                  });
  
                  options.complete = function() {
                      deferred.resolve(el);
                  };
  
                  duration && (options.duration = duration);
  
                  if (jsFn && isFunction(jsFn)) {
                      if (before) {
                          extend(el.style, before, true, false);
                      }
                      startCallback && startCallback(el);
                      data(el, dataParam, jsFn.call(context, el, function(){
                          deferred.resolve(el);
                      }));
                      return deferred;
                  }
                  else if (window.jQuery) {
  
                      var j = $(el);
                      before && j.css(before);
                      data(el, dataParam, "stop");
  
                      if (jsFn && isString(jsFn)) {
                          j[jsFn](options);
                          return deferred;
                      }
                      else if (after) {
                          j.animate(after, options);
                          return deferred;
                      }
                  }
              }
          }
  
          // no animation happened
  
          if (startCallback) {
              var promise = startCallback(el);
              if (isThenable(promise)) {
                  promise.done(function(){
                      deferred.resolve(el);
                  });
              }
              else {
                  deferred.resolve(el);
              }
          }
          else {
              deferred.resolve(el);
          }
  
          return deferred;
      };
  
      animate.addAnimationType     = function(name, stages) {
          types[name] = stages;
      };
  
      animate.stop = stopAnimation;
      animate.getPrefixes = getAnimationPrefixes;
      animate.getDuration = getAnimationDuration;
  
      /**
       * @function animate.cssAnimationSupported
       * @returns {bool}
       */
      animate.cssAnimationSupported = cssAnimSupported;
  
      return animate;
  }();
  
  
  
  
  
  
  /*
  
  value is always an object in the end
  {class: "condition", class: "condition"}
  
  array turns into _: []
  {_: [class, class]}
  (which is then turned into {class: true, class: true}
  
  
  DO NOT put class="{}" when using class.name="{}"
  
   */
  
  
  (function(){
  
      var toggleClass = function(node, cls, toggle, doAnim) {
  
          var has;
  
          if (toggle !== null) {
              if (toggle === hasClass(node, cls)) {
                  return;
              }
              has = !toggle;
          }
          else {
              has = hasClass(node, cls);
          }
  
          if (has) {
              if (doAnim) {
                  animate(node, [cls + "-remove"]).done(function(){
                      removeClass(node, cls);
                  });
              }
              else {
                  removeClass(node, cls);
              }
          }
          else {
              if (doAnim) {
                  animate(node, [cls + "-add"]).done(function(){
                      addClass(node, cls);
                  });
              }
              else {
                  addClass(node, cls);
              }
          }
      };
  
      var flatten = function(obj) {
  
          var list = {},
              i, j, l;
  
          if (!obj) {
              return list;
          }
  
          if (isString(obj)) {
              list[obj] = true;
          }
          else if (isArray(obj)) {
              for (i = -1, l = obj.length; ++i < l; list[obj[i]] = true){}
          }
          else {
              for (i in obj) {
                  if (i === '_') {
                      for (j = -1, l = obj._.length; ++j < l;
                           list[obj._[j]] = true){}
                  }
                  else {
                      list[i] = obj[i];
                  }
              }
          }
  
          return list;
      };
  
      Directive.registerAttribute("class", 1000, defineClass({
  
          $class: "Directive.attr.Class",
          $extends: Directive,
          initial: true,
          animate: false,
  
          $init: function(scope, node, expr, renderer, attr) {
  
              var self = this, 
                  values = attr ? attr.values : null,
                  cfg = attr ? attr.config : {},
                  k,
                  parts;
  
              self.animate = !!cfg.animate;
  
              if (values) {
                  parts = [];
                  if (expr) {
                      if (expr.substr(0,1) != '[') {
                          expr = '[' + expr + ']';
                      }
                      parts.push('_: ' + expr);
                  }
                  for (k in values) {
                      parts.push("'" + k + "'" + ': ' + values[k]);
                  }
                  expr = '{' + parts.join(', ') + '}';
              }
  
              this.$super(scope, node, expr);
          },
  
          onChange: function() {
  
              var self    = this,
                  node    = self.node,
                  clss    = flatten(self.watcher.getLastResult()),
                  prev    = flatten(self.watcher.getPrevValue()),
                  i;
  
              stopAnimation(node);
  
              for (i in prev) {
                  if (prev.hasOwnProperty(i)) {
                      if (clss[i] === undf) {
                          toggleClass(node, i, false, false);
                      }
                  }
              }
  
              for (i in clss) {
                  if (clss.hasOwnProperty(i)) {
                      toggleClass(node, i, !!clss[i], !self.initial && self.animate);
                  }
              }
  
              self.initial = false;
          }
      }));
  
  }());
  
  
  
  function toFragment(nodes, doc) {
  
      var fragment = (doc || window.document).createDocumentFragment(),
          i, l;
  
      if (isString(nodes)) {
          var tmp = window.document.createElement('div');
          tmp.innerHTML = nodes;
          nodes = tmp.childNodes;
      }
  
      if (!nodes) {
          return fragment;
      }
  
      if (nodes.nodeType) {
          fragment.appendChild(nodes);
      }
      else {
          // due to a bug in jsdom, we turn NodeList into array first
          if (nodes.item) {
              var tmpNodes = nodes;
              nodes = [];
              for (i = -1, l = tmpNodes.length >>> 0; ++i !== l; nodes.push(tmpNodes[i])) {}
          }
  
          for (i = -1, l = nodes.length; ++i !== l; fragment.appendChild(nodes[i])) {}
      }
  
      return fragment;
  };
  
  
  
  /**
   * @param {[]|Element} node
   * @returns {[]|Element}
   */
  var clone = function clone(node) {
  
      var i, len, cloned;
  
      if (isArray(node)) {
          cloned = [];
          for (i = 0, len = node.length; i < len; i++) {
              cloned.push(clone(node[i]));
          }
          return cloned;
      }
      else if (node) {
          switch (node.nodeType) {
              // element
              case 1:
                  return node.cloneNode(true);
              // text node
              case 3:
                  return window.document.createTextNode(node.innerText || node.textContent);
              // document fragment
              case 11:
                  return node.cloneNode(true);
  
              default:
                  return null;
          }
      }
  
      return null;
  };
  
  
  
  var rToCamelCase = /-./g;
  
  function toCamelCase(str) {
      return str.replace(rToCamelCase, function(match){
          return match.charAt(1).toUpperCase();
      });
  };
  
  
  
  var getAttrSet = (function() {
  
  
      // regular expression seems to be a few milliseconds faster
      // than plain parsing
      var reg = /^([\[({#$])([^)\]}"']+)[\])}]?$/;
  
      var removeDirective = function removeDirective(node, directive) {
          if (this.directive[directive] && 
              this.directive[directive].original) {
              removeAttr(node, this.directive[directive].original);
          }
          var i, l, sn = this.subnames[directive];
          if (sn) {
              for (i = 0, l = sn.length; i < l; i++) {
                  removeAttr(node, sn[i]);
              }
              delete this.subnames[directive];
          }
      };
  
      return function getAttrSet(node, lookupDirective) {
  
          var set = {
                  directive: {},
                  attribute: {},
                  config: {},
                  rest: {},
                  reference: null,
                  subnames: {},
                  removeDirective: removeDirective
              },
              i, l, tagName,
              name, value,
              match, parts,
              coll, mode,
              subname,
              attrs = isArray(node) ? node : node.attributes;
  
          for (i = 0, l = attrs.length; i < l; i++) {
  
              name = attrs[i].name;
              value = attrs[i].value;
              mode = null;
              match = name.match(reg);
  
              if (match) {
                  name = match[2];
                  mode = match[1];
  
                  if (mode === '#') {
                      set.reference = name;
                      continue;
                  }
              }
              else {
                  if (name.substr(0, 4) === "mjs-") {
                      name = name.substr(4);
                      mode = '{';
                  }
                  else {
                      set['rest'][name] = value;
                      continue;
                  }
              }
  
              parts = name.split(".");
              name = parts.shift();
  
              if (mode === '$') {
                  if (value === "") {
                      value = true;
                  }
  
                  tagName = node.tagName.toLowerCase();
  
                  set['config'][toCamelCase(name)] = value;
  
                  if (!set['subnames'][tagName]) {
                      set['subnames'][tagName] = [];
                  }
  
                  set['subnames'][tagName].push(attrs[i].name);
              }
              else if (mode === '(' || mode === '{') { 
  
                  coll = set['directive'];
                  subname = parts.length ? parts[0] : null;
  
                  if (!coll[name]) {
                      coll[name] = {
                          name: name,
                          original: null,
                          config: {},
                          value: null,
                          values: null
                      };
                  }
  
                  if (!subname) {
                      coll[name].original = attrs[i].name;
                  }
  
                  if (subname && !set['subnames'][name]) {
                      set['subnames'][name] = [];
                  }
  
                  if (subname && subname[0] === '$') {
                      if (value === "") {
                          value = true;
                      }
                      coll[name].config[toCamelCase(subname.substr(1))] = value;
                      set['subnames'][name].push(attrs[i].name);
                  }
                  else {
                      if (subname) {
                          if (!coll[name].values) {
                              coll[name].values = {};
                          }
                          // directive value keys are not camelcased
                          // do this inside directive if needed
                          // ('class' directive needs originals)
                          coll[name].values[parts.join(".")] = value;
                          set['subnames'][name].push(attrs[i].name);
                      }
                      else {
                          coll[name].value = value;
                      }
                  }
              }
              else if (mode === '[') {
                  set['attribute'][name] = {
                      value: value,
                      original: attrs[i].name
                  };
              }
          }
  
          return set;
      }
  
  }());
  
  
  
  
  
  
  var Renderer = function(){
  
      var handlers                = null,
          createText              = TextRenderer.create,
  
          lookupDirective = function(name) {
              return !!nsGet("directive.attr." + name, true);
          },
  
          nodeChildren = function(res, el, fn, fnScope, finish, cnt) {
  
              var children = [],
                  i, len;
  
              if (res && res !== true) {
                  if (res.nodeType) {
                      cnt.countdown += 1;
                      eachNode(res, fn, fnScope, finish, cnt);
                      return;
                  }
                  else {
                      children = slice.call(res);
                  }
              }
  
              if (!children.length) {
                  children = toArray(el.childNodes || el);
              }
  
              len = children.length;
  
              cnt.countdown += len;
  
              for(i = -1;
                  ++i < len;
                  eachNode(children[i], fn, fnScope, finish, cnt)){}
          },
  
  
          collectNodes    = function(coll, add) {
  
              if (add) {
                  if (add.nodeType) {
                      coll.push(add);
                  }
                  else if (isArray(add)) {
                      for (var i = -1, l = add.length; ++i < l; collectNodes(coll, add[i])){}
                  }
              }
          },
  
          //rSkipTag = /^(script|template|mjs-template|style)$/i,
  
          skipMap = {
              "script": true,
              "template": true,
              "mjs-template": true,
              "style": true,
              "link": true
          },
  
          eachNode = function(el, fn, fnScope, finish, cnt) {
  
              if (!el) {
                  return;
              }
  
              var res,
                  tag = el.nodeName;
  
              if (!cnt) {
                  cnt = {countdown: 1};
              }
  
              if (tag && skipMap[tag.toLowerCase()]) { //tag.match(rSkipTag)) {
                  --cnt.countdown === 0 && finish && finish.call(fnScope);
                  return;
              }
  
              res = fn.call(fnScope, el);
  
              if (res !== false) {
  
                  if (isThenable(res)) {
  
                      res.done(function(response){
  
                          if (response !== false) {
                              nodeChildren(response, el, fn, fnScope, finish, cnt);
                          }
  
                          --cnt.countdown === 0 && finish && finish.call(fnScope);
                      });
                      return; // prevent countdown
                  }
                  else {
                      nodeChildren(res, el, fn, fnScope, finish, cnt);
                  }
              }
  
              --cnt.countdown === 0 && finish && finish.call(fnScope);
          },
  
          observer = new Observable;
  
      return defineClass({
  
          $class: "Renderer",
  
          id: null,
          el: null,
          scope: null,
          texts: null,
          parent: null,
          passedAttrs: null,
          reportFirstNode: true,
  
          $init: function(el, scope, parent, passedAttrs) {
              var self            = this;
  
              self.id             = nextUid();
              self.el             = el;
              self.scope          = scope;
              self.texts          = [];
              self.parent         = parent;
              self.passedAttrs    = passedAttrs;
  
              if (scope instanceof Scope) {
                  scope.$on("destroy", self.$destroy, self);
              }
  
              if (parent) {
                  parent.on("destroy", self.$destroy, self);
              }
          },
  
          on: function(event, fn, context) {
              return observer.on(event + '-' + this.id, fn, context);
          },
  
          once: function(event, fn, context) {
              return observer.once(event + '-' + this.id, fn, context);
          },
  
          un: function(event, fn, context) {
              return observer.un(event + '-' + this.id, fn, context);
          },
  
          createChild: function(node) {
              return new Renderer(node, this.scope, this);
          },
  
          getEl: function() {
              return this.el;
          },
  
          runHandler: function(f, parentScope, node, attr, attrs) {
  
              var self    = this,
                  scope   = f.$isolateScope ?
                              parentScope.$newIsolated() :
                            (f.$breakScope  ?
                             parentScope.$new() :
                             parentScope),
                  app     = parentScope.$app,
                  value   = attr ? attr.value : null,
                  // attribute directives receive mods,
                  // tag directives receive cmpConfig
                  inject  = {
                      $scope: scope,
                      $node: node,
                      $attr: attr,
                      $attrValue: value,
                      $attrMap: attrs,
                      $renderer: self
                  },
                  args    = [scope, node, value, self, attr],
                  inst;
  
              if (attrs.reference) {
                  scope[attrs.reference] = node;
              }
  
              if (app) {
                  inst = app.inject(f, null, inject, args);
              }
              else if (f.$instantiate) {
                  inst = f.$instantiate.apply(f, args);
              }
              else {
                  inst = f.apply(null, args);
              }
  
              if (app && f.$registerBy && inst) {
                  if (isThenable(inst)) {
                      inst.done(function(cmp){
                          app.registerCmp(cmp, parentScope, f.$registerBy);
                      });
                  }
                  else {
                      app.registerCmp(inst, parentScope, f.$registerBy);
                  }
              }
  
  
              if (inst && inst.$destroy) {
                  self.on("destroy", inst.$destroy, inst);
              }
              else if (typeof inst === "function") {
                  self.on("destroy", inst);
              }
  
              if (f.$stopRenderer) {
                  return false;
              }
  
              if (inst && inst.getChildren) {
                  return inst.getChildren();
              }
  
              return inst;
          },
  
  
          processNode: function(node) {
  
              var self        = this,
                  nodeType    = node.nodeType,
                  texts       = self.texts,
                  scope       = self.scope,
                  textRenderer;
  
              // text node
              if (nodeType === 3) {
                  textRenderer    = createText(
                      scope,
                      node.textContent || node.nodeValue,
                      {userData: texts.length});
  
                  if (textRenderer) {
                      textRenderer.subscribe(self.onTextChange, self);
                      texts.push({
                          node: node,
                          tr: textRenderer
                      });
                      self.renderText(texts.length - 1);
                  }
              }
  
              // element node
              else if (nodeType === 1) {
  
                  if (self.reportFirstNode) {
                      observer.trigger("first-node-" + self.id, node);
                      self.reportFirstNode = false;
                  }
  
                  if (!handlers) {
                      handlers = Directive.getAttributes();
                  }
  
                  var tag     = node.tagName.toLowerCase(),
                      defers  = [],
                      nodes   = [],
                      i, l, f, len, c,
                      attrs, as,
                      attrProps,
                      name,
                      res,
                      handler,
                      someHandler = false;
  
                  if (tag.substr(0, 4) === "mjs-") {
                      tag = tag.substr(4);
                  }
  
                  attrs = getAttrSet(node, lookupDirective);
  
                  if (attrs.config.ignore) {
                      return false;
                  }
  
                  if (self.passedAttrs) {
                      attrs['directive'] = extend(
                          {}, 
                          attrs['directive'], 
                          self.passedAttrs['directive'], 
                          true, true
                      );
                      self.passedAttrs = null;
                  }
  
                  // this tag represents component
                  // we just pass it to attr.cmp directive
                  // by adding it to the attr map
                  if (c = nsGet("directive.component." + tag, true)) {
  
                      as = attrs.config.as || c.tag;
  
                      if (as) {
  
                          attrs["directive"]['cmp'] = {
                              value: c.prototype.$class,
                              name: "cmp",
                              original: "{cmp}",
                              config: extend({}, attrs.config),
                              values: null
                          };
  
                          as = window.document.createElement(as);
                          node.parentNode.replaceChild(as, node);
                          while (node.firstChild) {
                              as.appendChild(node.firstChild);
                          }
                          node = as;
                          for (name in attrs.rest) {
                              setAttr(node, name, attrs.rest[name]);
                          }
                      }
                      else {
  
                          f = nsGet("directive.attr.cmp", true);
                          var passAttrs = extend({}, attrs);
                          delete passAttrs['directive']['cmp'];
  
                          attrs.config.passAttrs = passAttrs;
  
                          res = self.runHandler(f, scope, node, {
                              value: c,
                              config: attrs.config
                          }, attrs);
                          someHandler = true;
  
                          if (res === false) {
                              return false;
                          }
                          if (isThenable(res)) {
                              defers.push(res);
                          }
                          else {
                              collectNodes(nodes, res);
                          }
                      }
                  }
  
                  // this is a tag directive
                  else if (f = nsGet("directive.tag." + tag, true)) {
  
                      res = self.runHandler(
                          f, scope, node, 
                          {value: null, config: attrs.config}, 
                          attrs
                      );
                      someHandler = true;
  
                      attrs.removeDirective(node, tag);
  
                      if (res === false) {
                          return false;
                      }
                      if (isThenable(res)) {
                          defers.push(res);
                      }
                      else {
                          collectNodes(nodes, res);
                      }
                  }
  
  
                  // this is an attribute directive
                  for (i = 0, len = handlers.length; i < len; i++) {
                      name    = handlers[i].name;
  
                      if ((attrProps = attrs['directive'][name]) !== undf &&
                          !attrProps.handled) {
  
                          handler = handlers[i].handler;
  
                          if (!handler.$keepAttribute) {
                              removeAttr(node, attrProps.original);
                          }
                          attrs.removeDirective(node, name);
  
                          res     = self.runHandler(handler, scope, node, attrProps, attrs);
  
                          someHandler = true;
                          attrProps.handled = true;
  
                          if (res === false) {
                              return false;
                          }
                          if (isThenable(res)) {
                              defers.push(res);
                          }
                          else {
                              collectNodes(nodes, res);
                          }
                      }
                  }
  
                  if (!someHandler && attrs.reference) {
                      scope[attrs.reference] = node;
                  }
  
                  if (defers.length && !attrs.config.ignoreInside) {
                      var deferred = new Promise;
                      Promise.all(defers).done(function(values){
                          collectNodes(nodes, values);
                          deferred.resolve(nodes);
                      });
                      return deferred;
                  }
  
                  // this is a plain attribute
                  for (i in attrs['attribute']) {
  
                      textRenderer = createText(
                          scope,
                          attrs['attribute'][i].value,
                          {userData: texts.length, force: true});
  
                      removeAttr(node, attrs['attribute'][i].original);
                      textRenderer.subscribe(self.onTextChange, self);
                      texts.push({
                          node: node,
                          attr: i,
                          //attrProp: attrs['attribute'][i],
                          tr: textRenderer
                      });
                      self.renderText(texts.length - 1);
                  }
  
                  if (attrs.config.ignoreInside) {
                      if (defers.length) {
                          var deferred = new Promise;
                          return Promise.all(defers).done(function(){
                              return deferred.resolve(false);
                          });
                          return deferred;
                      }
                      else {
                          return false;
                      }
                  }
  
                  return nodes.length ? nodes : true;
              }
  
              return true;
          },
  
          process: function() {
              var self    = this;
  
              if (self.el.nodeType) {
                  eachNode(self.el, self.processNode, self,
                      self.onProcessingFinished, {countdown: 1});
              }
              else {
                  nodeChildren(
                      null, self.el, self.processNode,
                      self, self.onProcessingFinished, {countdown: 0});
              }
          },
  
          onProcessingFinished: function() {
              observer.trigger("rendered-" + this.id, this);
          },
  
  
          onTextChange: function(textRenderer, inx) {
              this.renderText(inx);
          },
  
          renderText: function(inx) {
  
              var self        = this,
                  text        = self.texts[inx],
                  res         = text.tr.getString(),
                  attrName    = text.attr;
  
              if (attrName) {
  
                  if (attrName === "value") {
                      text.node.value = res;
                  }
                  else if (attrName === "class") {
                      text.node.className = res;
                  }
                  else if (attrName === "src") {
                      text.node.src = res;
                  }
  
                  setAttr(text.node, attrName, res);
              }
              else {
                  //text.node.textContent = res;
                  text.node.nodeValue = res;
              }
          },
  
  
          destroy: function() {
  
              var self    = this,
                  texts   = self.texts,
                  i, len;
  
              for (i = -1, len = texts.length; ++i < len; texts[i].tr.$destroy()) {}
  
              if (self.parent) {
                  self.parent.un("destroy", self.$destroy, self);
              }
  
              observer.trigger("destroy-" + self.id);
          }
  
      }, {
  
          setSkip: function(tag, value) {
              skipMap[tag] = value;
          }
      });
  
  }();
  
  
  
  
  
  var parseJSON = function() {
  
      return typeof JSON != strUndef ?
             function(data) {
                 return JSON.parse(data);
             } :
             function(data) {
                 return (new Function("return " + data))();
             };
  }();
  
  
  
  
  
  function parseXML(data, type) {
  
      var xml, tmp;
  
      if (!data || !isString(data)) {
          return null;
      }
  
      // Support: IE9
      try {
          tmp = new DOMParser();
          xml = tmp.parseFromString(data, type || "text/xml");
      } catch (thrownError) {
          xml = undf;
      }
  
      if (!xml || xml.getElementsByTagName("parsererror").length) {
          throw "Invalid XML: " + data;
      }
  
      return xml;
  };
  
  
  
  // partly from jQuery serialize.js
  
  var serializeParam = function(){
  
      var r20 = /%20/g,
          rbracket = /\[\]$/;
  
      function buildParams(prefix, obj, add) {
          var name,
              i, l, v;
  
          if (isArray(obj)) {
              // Serialize array item.
  
              for (i = 0, l = obj.length; i < l; i++) {
                  v = obj[i];
  
                  if (rbracket.test(prefix)) {
                      // Treat each array item as a scalar.
                      add(prefix, v);
  
                  } else {
                      // Item is non-scalar (array or object), encode its numeric index.
                      buildParams(
                          prefix + "[" + ( typeof v === "object" ? i : "" ) + "]",
                          v,
                          add
                      );
                  }
              }
          } else if (isPlainObject(obj)) {
              // Serialize object item.
              for (name in obj) {
                  buildParams(prefix + "[" + name + "]", obj[ name ], add);
              }
  
          } else {
              // Serialize scalar item.
              add(prefix, obj);
          }
      }
  
      return function(obj) {
  
          var prefix,
              s = [],
              add = function( key, value ) {
                  // If value is a function, invoke it and return its value
                  value = isFunction(value) ? value() : (value == null ? "" : value);
                  s[s.length] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
              };
  
          for ( prefix in obj ) {
              buildParams(prefix, obj[prefix], add);
          }
  
          // Return the resulting serialization
          return s.join( "&" ).replace( r20, "+" );
      };
  
  
  }();
  
  
  /**
   * @mixin Promise
   */
  ns.register("mixin.Promise", {
  
      $$promise: null,
  
      $beforeInit: function() {
          this.$$promise = new Promise;
      },
  
      then: function(){
          return this.$$promise.then.apply(this.$$promise, arguments);
      },
  
      done: function() {
          this.$$promise.done.apply(this.$$promise, arguments);
          return this;
      },
  
      always: function() {
          this.$$promise.always.apply(this.$$promise, arguments);
          return this;
      },
  
      fail: function() {
          this.$$promise.fail.apply(this.$$promise, arguments);
          return this;
      }
  
  });
  
  
  
  
  (function(){
  
  
  
      var accepts     = {
              xml:        "application/xml, text/xml",
              html:       "text/html",
              script:     "text/javascript, application/javascript",
              json:       "application/json, text/javascript",
              text:       "text/plain",
              _default:   "*/*"
          },
  
          createXHR       = function() {
  
              var xhr;
  
              if (!window.XMLHttpRequest || !(xhr = new XMLHttpRequest())) {
                  if (!(xhr = new ActiveXObject("Msxml2.XMLHTTP"))) {
                      if (!(xhr = new ActiveXObject("Microsoft.XMLHTTP"))) {
                          throw "Unable to create XHR object";
                      }
                  }
              }
  
              return xhr;
          },
  
          httpSuccess     = function(r) {
              try {
                  return (!r.status && location && location.protocol == "file:")
                         || (r.status >= 200 && r.status < 300)
                         || r.status === 304 || r.status === 1223; // || r.status === 0;
              } catch(thrownError){}
              return false;
          };
  
      return defineClass({
  
          $class: "ajax.transport.XHR",
  
          type: "xhr",
          _xhr: null,
          _deferred: null,
          _ajax: null,
  
          $init: function(opt, deferred, ajax) {
  
              var self    = this,
                  xhr;
  
              self._xhr = xhr     = createXHR();
              self._deferred      = deferred;
              self._opt           = opt;
              self._ajax          = ajax;
  
              if (opt.progress) {
                  xhr.onprogress = bind(opt.progress, opt.context);
              }
              if (opt.uploadProgress && xhr.upload) {
                  xhr.upload.onprogress = bind(opt.uploadProgress, opt.context);
              }
  
              xhr.onreadystatechange = bind(self.onReadyStateChange, self);
          },
  
          setHeaders: function() {
  
              var self = this,
                  opt = self._opt,
                  xhr = self._xhr,
                  i;
  
              if (opt.xhrFields) {
                  for (i in opt.xhrFields) {
                      xhr[i] = opt.xhrFields[i];
                  }
              }
              if (opt.data && opt.contentType) {
                  xhr.setRequestHeader("Content-Type", opt.contentType);
              }
              xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
              xhr.setRequestHeader("Accept",
                  opt.dataType && accepts[opt.dataType] ?
                  accepts[opt.dataType] + ", */*; q=0.01" :
                  accepts._default
              );
              for (i in opt.headers) {
                  xhr.setRequestHeader(i, opt.headers[i]);
              }
  
          },
  
          onReadyStateChange: function() {
  
              var self        = this,
                  xhr         = self._xhr,
                  deferred    = self._deferred;
  
              if (xhr.readyState === 0) {
                  xhr.onreadystatechange = emptyFn;
                  deferred.resolve(xhr);
                  return;
              }
  
              if (xhr.readyState === 4) {
                  xhr.onreadystatechange = emptyFn;
  
                  if (httpSuccess(xhr)) {
  
                      self._ajax.processResponse(
                          isString(xhr.responseText) ? xhr.responseText : undf,
                          xhr.getResponseHeader("content-type") || ''
                      );
                  }
                  else {
  
                      xhr.responseData = null;
  
                      try {
                          xhr.responseData = self._ajax.returnResponse(
                              isString(xhr.responseText) ? xhr.responseText : undf,
                              xhr.getResponseHeader("content-type") || ''
                          );
                      }
                      catch (thrownErr) {}
  
                      deferred.reject(xhr);
                  }
              }
          },
  
          abort: function() {
              var self    = this;
              self._xhr.onreadystatechange = emptyFn;
              self._xhr.abort();
          },
  
          send: function() {
  
              var self    = this,
                  opt     = self._opt;
  
              try {
                  self._xhr.open(opt.method, opt.url, true, opt.username, opt.password);
                  self.setHeaders();
                  self._xhr.send(opt.data);
              }
              catch (thrownError) {
                  if (self._deferred) {
                      self._deferred.reject(thrownError);
                  }
              }
          }
      });
  
  }());
  
  
  
  
  
  
  
  defineClass({
      $class: "ajax.transport.Script",
  
      type: "script",
      _opt: null,
      _deferred: null,
      _ajax: null,
      _el: null,
  
      $init: function(opt, deferred, ajax) {
          var self        = this;
  
          self._opt       = opt;
          self._ajax      = ajax;
          self._deferred  = deferred;
      },
  
      send: function() {
  
          var self    = this,
              script  = document.createElement("script");
  
          setAttr(script, "async", "async");
          setAttr(script, "charset", "utf-8");
          setAttr(script, "src", self._opt.url);
  
          addListener(script, "load", bind(self.onLoad, self));
          addListener(script, "error", bind(self.onError, self));
  
          document.head.appendChild(script);
  
          self._el = script;
      },
  
      onLoad: function(evt) {
          if (this._deferred) { // haven't been destroyed yet
              this._deferred.resolve(evt);
          }
      },
  
      onError: function(evt) {
          this._deferred.reject(evt);
      },
  
      abort: function() {
          var self    = this;
  
          if (self._el.parentNode) {
              self._el.parentNode.removeChild(self._el);
          }
      },
  
      destroy: function() {
  
          var self    = this;
  
          if (self._el.parentNode) {
              self._el.parentNode.removeChild(self._el);
          }
      }
  });
  
  
  
  
  
  defineClass({
  
      $class: "ajax.transport.IFrame",
  
      type: "iframe",
      _opt: null,
      _deferred: null,
      _ajax: null,
      _el: null,
      _sent: false,
  
      $init: function(opt, deferred, ajax) {
          var self        = this;
  
          self._opt       = opt;
          self._ajax      = ajax;
          self._deferred  = deferred;
      },
  
      send: function() {
  
          var self    = this,
              frame   = document.createElement("iframe"),
              id      = "frame-" + nextUid(),
              form    = self._opt.form;
  
          setAttr(frame, "id", id);
          setAttr(frame, "name", id);
          frame.style.display = "none";
          document.body.appendChild(frame);
  
          setAttr(form, "action", self._opt.url);
          setAttr(form, "target", id);
  
          addListener(frame, "load", bind(self.onLoad, self));
          addListener(frame, "error", bind(self.onError, self));
  
          self._el = frame;
  
          var tries = 0;
  
          var submit = function() {
  
              tries++;
  
              try {
                  form.submit();
                  self._sent = true;
              }
              catch (thrownError) {
                  if (tries > 2) {
                      self._deferred.reject(thrownError);
                  }
                  else {
                      async(submit, null, [], 1000);
                  }
              }
          };
  
          submit();
      },
  
      onLoad: function() {
  
          var self    = this,
              frame   = self._el,
              doc,
              data;
  
          if (!self._sent) {
              return;
          }
  
          if (self._opt && !self._opt.jsonp) {
  
              try {
                  doc = frame.contentDocument || frame.contentWindow.document;
                  data = doc.body.innerHTML;
                  self._ajax.processResponse(data);
              }
              catch (thrownError) {
                  self._deferred.reject(thrownError);
              }
          }
      },
  
      onError: function(evt) {
  
          if (!this._sent) {
              return;
          }
  
          this._deferred.reject(evt);
      },
  
      abort: function() {
          var self    = this;
  
          if (self._el.parentNode) {
              self._el.parentNode.removeChild(self._el);
          }
      },
  
      destroy: function() {
          var self    = this;
  
          if (self._el.parentNode) {
              self._el.parentNode.removeChild(self._el);
          }
      }
  
  });
  
  
  
  
  
  
  
  
  
  (function(){
  
      var rquery          = /\?/,
          rurl            = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
          rhash           = /#.*$/,
          rts             = /([?&])_=[^&]*/,
          rgethead        = /^(?:GET|HEAD)$/i,
  
          globalEvents    = new Observable,
  
          formDataSupport = !!(window && window.FormData),
  
          processData     = function(data, opt, ct) {
  
              var type        = opt ? opt.dataType : null,
                  selector    = opt ? opt.selector : null,
                  doc;
  
              if (!isString(data)) {
                  return data;
              }
  
              ct = ct || "";
  
              if (type === "xml" || !type && ct.indexOf("xml") >= 0) {
                  doc = parseXML(trim(data));
                  return selector ? select(selector, doc) : doc;
              }
              else if (type === "html") {
                  doc = parseXML(data, "text/html");
                  return selector ? select(selector, doc) : doc;
              }
              else if (type == "fragment") {
                  var fragment    = document.createDocumentFragment(),
                      div         = document.createElement("div");
  
                  div.innerHTML   = data;
  
                  while (div.firstChild) {
                      fragment.appendChild(div.firstChild);
                  }
  
                  return fragment;
              }
              else if (type === "json" || !type && ct.indexOf("json") >= 0) {
                  return parseJSON(trim(data));
              }
              else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
                  globalEval(data);
              }
  
              return data + "";
          },
  
  
          fixUrlDomain    = function(url) {
  
              if (url.substr(0,1) == "/") {
                  return location.protocol + "//" + location.host + url;
              }
              else {
                  return url;
              }
          },
  
          prepareUrl  = function(url, opt) {
  
              url.replace(rhash, "");
  
              if (!opt.allowCache) {
  
                  var stamp   = (new Date).getTime();
  
                  url = rts.test(url) ?
                      // If there is already a '_' parameter, set its value
                        url.replace(rts, "$1_=" + stamp) :
                      // Otherwise add one to the end
                        url + (rquery.test(url) ? "&" : "?" ) + "_=" + stamp;
              }
  
              if (opt.data && opt.method != "POST" && !opt.contentType && (!formDataSupport || !(opt.data instanceof window.FormData))) {
  
                  opt.data = !isString(opt.data) ? serializeParam(opt.data) : opt.data;
                  url += (rquery.test(url) ? "&" : "?") + opt.data;
                  opt.data = null;
              }
  
              return url;
          },
  
          data2form       = function(data, form, name) {
  
              var i, input, len;
  
              if (!isObject(data) && !isFunction(data) && name) {
                  input   = document.createElement("input");
                  setAttr(input, "type", "hidden");
                  setAttr(input, "name", name);
                  setAttr(input, "value", data);
                  form.appendChild(input);
              }
              else if (isArray(data) && name) {
                  for (i = 0, len = data.length; i < len; i++) {
                      data2form(data[i], form, name + "["+i+"]");
                  }
              }
              else if (isObject(data)) {
                  for (i in data) {
                      if (data.hasOwnProperty(i)) {
                          data2form(data[i], form, name ? name + "["+i+"]" : i);
                      }
                  }
              }
          },
  
  
          // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
          serializeForm   = function(form) {
  
              var oField, sFieldType, nFile, obj = {};
  
              for (var nItem = 0; nItem < form.elements.length; nItem++) {
  
                  oField = form.elements[nItem];
  
                  if (getAttr(oField, "name") === null) {
                      continue;
                  }
  
                  sFieldType = oField.nodeName.toUpperCase() === "INPUT" ?
                               getAttr(oField, "type").toUpperCase() : "TEXT";
  
                  if (sFieldType === "FILE") {
                      for (nFile = 0;
                           nFile < oField.files.length;
                           obj[oField.name] = oField.files[nFile++].name){}
  
                  } else if ((sFieldType !== "RADIO" && sFieldType !== "CHECKBOX") || oField.checked) {
                      obj[oField.name] = oField.value;
                  }
              }
  
              return serializeParam(obj);
          },
  
          globalEval = function(code){
              var script, indirect = eval;
              if (code) {
                  if (/^[^\S]*use strict/.test(code)) {
                      script = document.createElement("script");
                      script.text = code;
                      document.head.appendChild(script)
                          .parentNode.removeChild(script);
                  } else {
                      indirect(code);
                  }
              }
          };
  
      defineClass({
  
          $class: "Ajax",
          $mixins: ["mixin.Promise"],
  
          _jsonpName: null,
          _transport: null,
          _opt: null,
          _deferred: null,
          _promise: null,
          _timeout: null,
          _form: null,
          _removeForm: false,
  
          $init: function(opt) {
  
              if (opt.url) {
                  opt.url = fixUrlDomain(opt.url);
              }
  
              var self        = this,
                  href        = window ? window.location.href : "",
                  local       = rurl.exec(href.toLowerCase()) || [],
                  parts       = rurl.exec(opt.url.toLowerCase());
  
              self._opt       = opt;
  
              if (opt.crossDomain !== true && opt.ignoreCrossDomain !== true) {
                  opt.crossDomain = !!(parts &&
                                       (parts[1] !== local[1] || parts[2] !== local[2] ||
                                        (parts[3] || (parts[1] === "http:" ? "80" : "443")) !==
                                        (local[3] || (local[1] === "http:" ? "80" : "443"))));
              }
  
              //deferred    = new Promise,
              var transport;
  
              if (opt.files) {
                  if (!formDataSupport) {
                      opt.transport = "iframe";
                  }
              }
  
              if (opt.transport == "iframe" && !opt.form) {
                  self.createForm();
                  opt.form = self._form;
              }
              else if (opt.form) {
                  self._form = opt.form;
                  if (opt.method == "POST" && !formDataSupport) {
                      opt.transport = "iframe";
                  }
              }
  
              if (opt.form && opt.transport != "iframe" && opt.method == "POST") {
                  if (formDataSupport) {
                      opt.data = new FormData(opt.form);
                  }
                  else {
                      opt.contentType = "application/x-www-form-urlencoded";
                      opt.data = serializeForm(opt.form);
                  }
              }
              else if (opt.contentType == "json") {
                  opt.contentType = opt.contentTypeHeader || "text/plain";
                  opt.data = isString(opt.data) ? opt.data : JSON.stringify(opt.data);
              }
              else if (isPlainObject(opt.data) && opt.method == "POST" && formDataSupport) {
  
                  var d = opt.data,
                      k;
  
                  opt.data = new FormData;
  
                  for (k in d) {
                      opt.data.append(k, d[k]);
                  }
              }
  
              if (opt.files) {
                  self.importFiles();
              }
  
              opt.url = prepareUrl(opt.url, opt);
  
              if ((opt.crossDomain || opt.transport == "script") && !opt.form) {
                  transport   = new MetaphorJs.ajax.transport.Script(opt, self.$$promise, self);
              }
              else if (opt.transport == "iframe") {
                  transport   = new MetaphorJs.ajax.transport.IFrame(opt, self.$$promise, self);
              }
              else {
                  transport   = new MetaphorJs.ajax.transport.XHR(opt, self.$$promise, self);
              }
  
              //self._deferred      = deferred;
              self._transport     = transport;
  
              self.$$promise.done(function(value) {
                  globalEvents.trigger("success", value);
              });
              self.$$promise.fail(function(reason) {
                  globalEvents.trigger("error", reason);
              });
              self.$$promise.always(function(){
                  globalEvents.trigger("end");
              });
  
              globalEvents.trigger("start");
  
  
              if (opt.timeout) {
                  self._timeout = setTimeout(bind(self.onTimeout, self), opt.timeout);
              }
  
              if (opt.jsonp) {
                  self.createJsonp();
              }
  
              if (globalEvents.trigger("before-send", opt, transport) === false) {
                  //self._promise = Promise.reject();
                  self.$$promise.reject();
              }
              if (opt.beforeSend && opt.beforeSend.call(opt.context, opt, transport) === false) {
                  //self._promise = Promise.reject();
                  self.$$promise.reject();
              }
  
              if (self.$$promise.isPending()) {
                  async(transport.send, transport);
  
                  //deferred.abort = bind(self.abort, self);
                  self.$$promise.always(self.asyncDestroy, self);
  
                  //self._promise = deferred;
              }
              else {
                  async(self.asyncDestroy, self, [], 1000);
              }
          },
  
          asyncDestroy: function() {
  
              var self = this;
  
              if (self.$isDestroyed()) {
                  return;
              }
  
              if (self.$$promise.hasListeners()) {
                  async(self.asyncDestroy, self, [], 1000);
                  return;
              }
  
              self.$destroy();
          },
  
          /*promise: function() {
              return this._promise;
          },*/
  
          abort: function(reason) {
              this.$$promise.reject(reason || "abort");
              this._transport.abort();
              //this._deferred.reject(reason || "abort");
              return this;
          },
  
          onTimeout: function() {
              this.abort("timeout");
          },
  
          getTransport: function() {
              return this._transport;
          },
  
          createForm: function() {
  
              var self    = this,
                  form    = document.createElement("form");
  
              form.style.display = "none";
              setAttr(form, "method", self._opt.method);
              setAttr(form, "enctype", "multipart/form-data");
  
              data2form(self._opt.data, form, null);
  
              document.body.appendChild(form);
  
              self._form = form;
              self._removeForm = true;
          },
  
          importFiles: function() {
  
              var self    = this,
                  opt     = self._opt,
                  files   = opt.files,
                  tr      = opt.transport,
                  form    = self._form,
                  data    = opt.data,
                  i, l,
                  j, jl,
                  name,
                  input,
                  file,
                  item;
  
              for (i = 0, l = files.length; i < l; i++) {
  
                  item = files[i];
  
                  if (isArray(item)) {
                      name = item[0];
                      file = item[1];
                  }
                  else {
                      if (window.File && item instanceof File) {
                          name = item.uploadName || ("upload" + (l > 1 ? "[]" : ""));
                      }
                      else {
                          name = item.name || "upload" + (l > 1 ? "[]" : "");
                      }
                      file = item;
                  }
  
                  if (!window.File || !(file instanceof File)) {
                      input = file;
                      file = null;
                  }
  
                  if (form) {
                      if (input) {
                          form.appendChild(input);
                      }
                  }
                  else {
                      if (file) {
                          data.append(name, file);
                      }
                      else if (input.files && input.files.length) {
                          for (j = 0, jl = input.files.length; j < jl; j++) {
                              data.append(name, input.files[j]);
                          }
                      }
                  }
              }
          },
  
          createJsonp: function() {
  
              var self        = this,
                  opt         = self._opt,
                  paramName   = opt.jsonpParam || "callback",
                  cbName      = opt.jsonpCallback || "jsonp_" + nextUid();
  
              opt.url += (rquery.test(opt.url) ? "&" : "?") + paramName + "=" + cbName;
  
              self._jsonpName = cbName;
  
              if (typeof window != strUndef) {
                  window[cbName] = bind(self.jsonpCallback, self);
              }
              if (typeof global != strUndef) {
                  global[cbName] = bind(self.jsonpCallback, self);
              }
  
              return cbName;
          },
  
          jsonpCallback: function(data) {
  
              var self    = this,
                  res;
  
              try {
                  res = self.processResponseData(data);
              }
              catch (thrownError) {
                  if (self.$$promise) {
                      self.$$promise.reject(thrownError);
                  }
                  else {
                      error(thrownError);
                  }
              }
  
              if (self.$$promise) {
                  self.$$promise.resolve(res);
              }
          },
  
          processResponseData: function(data, contentType) {
  
              var self    = this,
                  opt     = self._opt;
  
              data    = processData(data, opt, contentType);
  
              if (globalEvents.hasListener("process-response")) {
                  globalEvents.trigger("process-response", data, self.$$promise);
              }
  
              if (opt.processResponse) {
                  data    = opt.processResponse.call(opt.context, data, self.$$promise);
              }
  
              return data;
          },
  
          returnResponse: function(data, contentType) {
  
              var self    = this;
  
              if (!self._opt.jsonp) {
                  return self.processResponseData(data, contentType);
              }
  
              return null;
          },
  
          processResponse: function(data, contentType) {
  
              var self        = this,
                  deferred    = self.$$promise,
                  result;
  
              if (!self._opt.jsonp) {
                  try {
                      result = self.processResponseData(data, contentType)
                  }
                  catch (thrownError) {
                      deferred.reject(thrownError);
                  }
  
                  deferred.resolve(result);
              }
              else {
                  if (!data) {
                      deferred.reject("jsonp script is empty");
                      return;
                  }
  
                  try {
                      globalEval(data);
                  }
                  catch (thrownError) {
                      deferred.reject(thrownError);
                  }
  
                  if (deferred.isPending()) {
                      deferred.reject("jsonp script didn't invoke callback");
                  }
              }
          },
  
          destroy: function() {
  
              var self    = this;
  
              if (self._timeout) {
                  clearTimeout(self._timeout);
              }
  
              if (self._form && self._form.parentNode && self._removeForm) {
                  self._form.parentNode.removeChild(self._form);
              }
  
              self._transport.$destroy();
  
              if (self._jsonpName) {
                  if (typeof window != strUndef) {
                      delete window[self._jsonpName];
                  }
                  if (typeof global != strUndef) {
                      delete global[self._jsonpName];
                  }
              }
          }
  
      }, {
  
          prepareUrl: prepareUrl,
          global: globalEvents
      });
  
  
  }());
  
  
  
  
  
  
  
  /*
  * Contents of this file are partially taken from jQuery
  */
  
  var ajax = function(){
  
      
  
      var defaults    = {
              url:            null,
              data:           null,
              method:         "GET",
              headers:        null,
              username:       null,
              password:       null,
              cache:          null,
              dataType:       null, // response data type
              timeout:        0,
              contentType:    null, // request data type
              xhrFields:      null,
              jsonp:          false,
              jsonpParam:     null,
              jsonpCallback:  null,
              transport:      null,
              replace:        false,
              selector:       null,
              form:           null,
              beforeSend:     null,
              progress:       null,
              uploadProgress: null,
              processResponse:null,
              context:        null
          },
  
          defaultSetup    = {};
  
  
      var ajax    = function(url, opt) {
  
          opt = opt || {};
  
          if (url && !isString(url)) {
              opt = url;
          }
          else {
              opt.url = url;
          }
  
          if (!opt.url) {
              if (opt.form) {
                  opt.url = getAttr(opt.form, "action");
              }
              if (!opt.url) {
                  throw "Must provide url";
              }
          }
  
          extend(opt, defaultSetup, false, true);
          extend(opt, defaults, false, true);
  
          if (!opt.method) {
              if (opt.form) {
                  opt.method = getAttr(opt.form, "method").toUpperCase() || "GET";
              }
              else {
                  opt.method = "GET";
              }
          }
          else {
              opt.method = opt.method.toUpperCase();
          }
  
          return new MetaphorJs.Ajax(opt);
      };
  
      ajax.setup  = function(opt) {
          extend(defaultSetup, opt, true, true);
      };
  
      ajax.on     = function() {
          MetaphorJs.Ajax.global.on.apply(MetaphorJs.Ajax.global, arguments);
      };
  
      ajax.un     = function() {
          MetaphorJs.Ajax.global.un.apply(MetaphorJs.Ajax.global, arguments);
      };
  
      ajax.get    = function(url, opt) {
          opt = opt || {};
          opt.method = "GET";
          return ajax(url, opt);
      };
  
      ajax.post   = function(url, opt) {
          opt = opt || {};
          opt.method = "POST";
          return ajax(url, opt);
      };
  
      ajax.load   = function(el, url, opt) {
  
          opt = opt || {};
  
          if (!isString(url)) {
              opt = url;
          }
  
          opt.dataType = "fragment";
  
          return ajax(url, opt).done(function(fragment){
              if (opt.replace) {
                  while (el.firstChild) {
                      el.removeChild(el.firstChild);
                  }
              }
              el.appendChild(fragment);
          });
      };
  
      ajax.loadScript = function(url) {
          return ajax(url, {transport: "script"});
      };
  
      ajax.submit = function(form, opt) {
  
          opt = opt || {};
          opt.form = form;
  
          return ajax(null, opt);
      };
  
      ajax.prepareUrl = function(url, opt) {
          return MetaphorJs.Ajax.prepareUrl(url, opt || {});
      };
  
      return ajax;
  }();
  
  
  
  
  
  
  
  
  
  
  var Template = function(){
  
      var observable      = new Observable,
          cache           = new Cache,
          options         = {},
  
          getFragmentContent  = function(frg) {
              var div = window.document.createElement("div");
              div.appendChild(clone(frg));
              return div.innerHTML;
          },
  
          resolveInclude  = function(cmt, tplId) {
              var frg = getTemplate(trim(tplId));
              if (!frg) {
                  return "";
              }
              if (typeof frg === "string") {
                  return frg;
              }
              return getFragmentContent(frg);
          },
  
          resolveIncludes = function(tpl) {
              return tpl.replace(/<!--\s*include (.+?)-->/ig, resolveInclude);
          },
  
          getTemplate     = function(tplId) {
  
              var tpl = cache.get(tplId),
                  opt = options[tplId] || {};
  
              if (typeof tpl === "function") {
                  tpl = tpl(tplId);
              }
              if (typeof tpl === "string" && !opt.text) {
                  if (!opt.processed) {
                      tpl = processTextTemplate(tplId, tpl);
                  }
                  tpl = toFragment(tpl);
                  cache.add(tplId, tpl);
              }
              else if (tpl && tpl.nodeType) {
                  if ("content" in tpl) {
                      tpl = tpl.content;
                  }
                  else {
                      tpl = toFragment(tpl.childNodes);
                  }
                  cache.add(tplId, tpl);
              }
  
              return tpl;
          },
  
          processTextTemplate = function(tplId, tpl) {
              if (tpl.substr(0,5) === "<!--{") {
                  var inx = tpl.indexOf("-->"),
                      opt = createGetter(tpl.substr(4, inx-4))({});
  
                  options[tplId] = opt;
                  options[tplId].processed = true;
  
                  tpl = tpl.substr(inx + 3);
  
                  if (opt.includes) {
                      tpl = resolveIncludes(tpl);
                  }
  
                  if (opt.text) {
                      return tpl;
                  }
              }
              
              if (!options[tplId]) {
                  options[tplId] = {};
              }
  
              options[tplId].processed = true;
  
              return toFragment(tpl);
          },
  
          findInPrebuilt = function(tplId) {
              if (__MetaphorJsPrebuilt['__tpls'][tplId]) {
                  tpl = __MetaphorJsPrebuilt['__tpls'][tplId];
                  delete __MetaphorJsPrebuilt['__tpls'][tplId];
                  return tpl;
                  //return processTextTemplate(tplId, tpl);
              }
          },
  
          findInScripts = function(tplId) {
              var tplNode = window.document.getElementById(tplId),
                  tpl,
                  tag;
  
              if (tplNode) {
                  tag = tplNode.tagName.toLowerCase();
                  if (tag === "script") {
                      tpl = tplNode.innerHTML;
                      tplNode.parentNode.removeChild(tplNode);
                      return tpl;
                  }
                  else {
                      return tplNode;
                  }
              }
          },
  
          loadTemplate = function(tplUrl) {
              if (!cache.exists(tplUrl)) {
                  return cache.add(tplUrl,
                      ajax(tplUrl, {dataType: 'fragment'})
                          .then(function(fragment){
                              return cache.add(tplUrl, fragment);
                          })
                  );
              }
              return cache.get(tplUrl);
          },
  
          isExpression = function(str) {
              if (str.substr(0,1) === '.') {
                  var second = str.substr(1,1);
                  return !(second === '.' || second === '/');
              }
              return str.substr(0,1) === '{' || str.substr(0,5) === 'this.';
          };
  
      if (typeof __MetaphorJsPrebuilt !== "undefined" &&
                  __MetaphorJsPrebuilt['__tpls']) {
          cache.addFinder(findInPrebuilt);
      }
  
      cache.addFinder(findInScripts);
  
      return defineClass({
  
          $class:             "Template",
  
          _watcher:           null,
          _tpl:               null,
          _renderer:          null,
          _initial:           true,
          _fragment:          null,
          _id:                null,
          _originalNode:      null,
          _intendedShadow:    false,
          _prevEl:            null,
          _nextEl:            null,
  
          scope:              null,
          node:               null,
          tpl:                null,
          url:                null,
          html:               null,
          ownRenderer:        true,
          initPromise:        null,
          tplPromise:         null,
          parentRenderer:     null,
          deferRendering:     false,
          replace:            false,
          shadow:             false,
          animate:            false,
  
          passAttrs:          null,
  
          $init: function(cfg) {
  
              var self    = this;
  
              extend(self, cfg, true, false);
  
              var shadowRootSupported =
                  !!window.document.documentElement.createShadowRoot;
  
              if (!shadowRootSupported) {
                  self._intendedShadow = self.shadow;
                  self.shadow = false;
              }
  
              self.id     = nextUid();
  
              if (!self.scope) {
                  self.scope = new Scope;
              }
  
              observable.createEvent("rendered-" + self.id, {
                  returnResult: false,
                  autoTrigger: true
              });
  
              self.tpl && (self.tpl = trim(self.tpl));
              self.url && (self.url = trim(self.url));
  
              var node    = self.node,
                  tpl     = self.tpl || self.url;
  
              //node && removeAttr(node, "include");
  
              if (self.replace && node) {
                  self._prevEl = window.document.createComment(self.id + " - start");
                  self._nextEl = window.document.createComment(self.id + " - end");
                  var parent = node.parentNode;
                  if (parent) {
                      parent.insertBefore(self._prevEl, node);
                      parent.insertBefore(self._nextEl, node.nextSibling);
                  }
              }
  
              if (self.shadow) {
                  self._originalNode = node;
                  self.node = node = node.createShadowRoot();
              }
  
              if (!node) {
                  self.deferRendering = true;
              }
  
              if (tpl) {
  
                  if (node && node.firstChild && !self.shadow) {
                      data(node, "mjs-transclude", toFragment(node.childNodes));
                  }
  
                  if (isExpression(tpl)) {
                      self._watcher = createWatchable(
                          self.scope,
                          tpl,
                          self.onChange,
                          self,
                          {filterLookup: filterLookup});
                      var val = self._watcher.getLastResult();
                      if (typeof val !== "string") {
                          extend(self, val, true, false);
                      }
                  }
  
                  self.resolveTemplate();
  
                  if (!self.deferRendering || !self.ownRenderer) {
                      self.tplPromise.done(self.applyTemplate, self);
                  }
              }
              else if (self.html) {
                  self._watcher = createWatchable(
                      self.scope,
                      self.html,
                      self.onHtmlChange,
                      self,
                      {filterLookup: filterLookup});
  
                  self.initPromise    = new Promise;
                  self.onHtmlChange();
              }
              else {
                  if (!self.deferRendering && self.ownRenderer) {
                      self.doRender();
                  }
              }
  
              // moved from if (tpl)
              if (self.ownRenderer && self.parentRenderer) {
                  self.parentRenderer.on("destroy",
                      self.onParentRendererDestroy,
                      self);
              }
  
              self.scope.$on("destroy", self.onScopeDestroy, self);
          },
  
          setAnimation: function(state) {
              this.animate = state;
          },
  
          doRender: function() {
              var self = this;
              if (!self._renderer) {
                  self._renderer   = new Renderer(self.node, self.scope, null, self.passAttrs);
                  self._renderer.on("rendered", self.onRendered, self);
                  self._renderer.on("first-node", self.onFirstNodeReported, self);
                  self._renderer.process();
              }
          },
  
          onFirstNodeReported: function(node) {
              observable.trigger("first-node-" + this.id, node);
          },
  
          onRendered: function() {
              observable.trigger("rendered-" + this.id, this);
          },
  
          on: function(event, fn, context) {
              return observable.on(event + "-" + this.id, fn, context);
          },
  
          un: function(event, fn, context) {
              return observable.un(event + "-" + this.id, fn, context);
          },
  
          startRendering: function() {
  
              var self    = this,
                  tpl     = self.tpl || self.url;
  
              if (self.deferRendering && (self.node || self.node === false)) {
  
                  self.deferRendering = false;
                  if (self.tplPromise) {
                      self.tplPromise.done(
                          tpl ? self.applyTemplate : self.doRender,
                          self
                      );
                      return self.initPromise;
                  }
                  else {
                      tpl ? self.applyTemplate() : self.doRender();
                  }
              }
  
              return null;
          },
  
          resolveTemplate: function() {
  
              var self    = this,
                  url     = self.url,
                  tpl     = self._watcher ?
                            self._watcher.getLastResult() :
                            (self.tpl || url);
  
              if (self._watcher && !tpl) {
                  url     = null;
              }
  
              if (tpl && typeof tpl !== "string") {
                  tpl     = tpl.tpl || tpl.url;
                  url     = null;
              }
  
              self.initPromise    = new Promise;
              self.tplPromise     = new Promise;
  
              if (self.ownRenderer) {
                  self.initPromise.resolve(false);
              }
  
              return new Promise(function(resolve, reject){
                  if (tpl || url) {
  
                      if (url) {
                          resolve(getTemplate(tpl) || loadTemplate(url));
                      }
                      else {
                          resolve(getTemplate(tpl) || toFragment(tpl));
                      }
                  }
                  else {
                      reject();
                  }
              })
                  .done(function(fragment){
                      self._fragment = fragment;
                      self.tplPromise.resolve();
                  })
                  .fail(self.initPromise.reject, self.initPromise)
                  .fail(self.tplPromise.reject, self.tplPromise);
          },
  
          onHtmlChange: function() {
              var self    = this,
                  el      = self.node;
  
              if (self._renderer) {
                  self._renderer.$destroy();
                  self._renderer = null;
              }
  
              var htmlVal = self._watcher.getLastResult();
  
              if (htmlVal) {
                  self._fragment = toFragment(htmlVal);
                  self.applyTemplate();
              }
              else if (el) {
                  while (el.firstChild) {
                      el.removeChild(el.firstChild);
                  }
              }
          },
  
          onChange: function() {
  
              var self    = this,
                  el      = self.node;
  
              if (self._renderer) {
                  self._renderer.$destroy();
                  self._renderer = null;
              }
  
              var tplVal = self._watcher.getLastResult();
  
              if (tplVal) {
                  self.resolveTemplate()
                      .done(self.applyTemplate, self);
              }
              else if (el) {
                  while (el.firstChild) {
                      el.removeChild(el.firstChild);
                  }
              }
          },
  
          doApplyTemplate: function() {
  
              var self    = this,
                  el      = self.node,
                  frg,
                  children,
                  i, l;
  
              if (el) {
                  if (self.replace) {
                      var next = self._nextEl, prev = self._prevEl;
                      while (prev.parentNode && prev.nextSibling && 
                              prev.nextSibling !== next) {
                          prev.parentNode.removeChild(prev.nextSibling);
                      }
                      /*for (i = 0, l = el.length; i < l; i++) {
                          if (el[i].parentNode) {
                              el[i].parentNode.removeChild(el[i]);
                          }
                      }*/
                  }
                  else if (el.firstChild) {
                      while (el.firstChild) {
                          el.removeChild(el.firstChild);
                      }
                  }
              }
  
              if (self._intendedShadow) {
                  self.makeTranscludes();
              }
  
              if (self.replace) {
  
                  frg = clone(self._fragment);
                  children = toArray(frg.childNodes);
  
                  if (el && el.nodeType) {
                      var transclude = el ? data(el, "mjs-transclude") : null;
  
                      if (transclude) {
                          var tr = select("[{transclude}], [mjs-transclude], mjs-transclude", frg);
                          if (tr.length) {
                              data(tr[0], "mjs-transclude", transclude);
                          }
                      }
  
                      el.parentNode && el.parentNode.removeChild(el);
                  }
  
                  self._nextEl.parentNode.insertBefore(frg, self._nextEl);
                  self.node = children;
                  self.initPromise.resolve(children);
              }
              else {
  
                  if (el) {
                      el.appendChild(clone(self._fragment));
                  }
                  else {
                      self.node = el = clone(self._fragment);
                  }
                  self.initPromise.resolve(el);
              }
  
              observable.trigger("before-render-" + self.id, self);
  
              if (self.ownRenderer) {
                  self.doRender();
              }
          },
  
          applyTemplate: function() {
  
              var self        = this,
                  el          = self.node,
                  deferred    = new Promise;
  
              if (!self._initial && self.animate) {
                  animate(el, "leave")
                      .done(self.doApplyTemplate, self)
                      .done(deferred.resolve, deferred);
                  animate(el, "enter");
              }
              else {
                  self.doApplyTemplate();
                  deferred.resolve();
              }
  
              self._initial = false;
  
              return deferred;
          },
  
          makeTranscludes: function() {
  
              var self    = this,
                  fr      = self._fragment,
                  cnts    = select("content", fr),
                  el, next,
                  tr, sel,
                  i, l;
  
              for (i = 0, l = cnts.length; i < l;  i++) {
  
                  tr      = window.document.createElement("transclude");
                  el      = cnts[i];
                  next    = el.nextSibling;
                  sel     = getAttr(el, "select");
  
                  sel && setAttr(tr, "select", sel);
  
                  fr.removeChild(el);
                  fr.insertBefore(tr, next);
              }
          },
  
          onParentRendererDestroy: function() {
              var self = this;
  
              if (!self.$destroyed && self._renderer &&
                  !self._renderer.$destroyed) {
                  self._renderer.$destroy();
              }
              self.$destroy();
          },
  
          onScopeDestroy: function() {
              this.$destroy();
          },
  
          destroy: function() {
  
              var self = this;
  
              if (self._nextEl && self._nextEl.parentNode) {
                  self._nextEl.parentNode.removeChild(self._nextEl);
              }
              
              if (self._prevEl && self._prevEl.parentNode) {
                  self._prevEl.parentNode.removeChild(self._prevEl);
              }
  
              if (self.shadow) {
                  self._originalNode.createShadowRoot();
              }
  
              if (self._watcher) {
                  if (self.html) {
                      self._watcher.unsubscribeAndDestroy(self.onHtmlChange, self);
                  }
                  else {
                      self._watcher.unsubscribeAndDestroy(self.onChange, self);
                  }
              }
          }
  
      }, {
          cache: cache
      });
  }();
  
  
  
  
  
  
  
  var Provider = function(){
  
      var VALUE       = 1,
          CONSTANT    = 2,
          FACTORY     = 3,
          SERVICE     = 4,
          PROVIDER    = 5,
          globalProvider;
  
      var Provider = function() {
          this.store  = {};
      };
  
      extend(Provider.prototype, {
  
          store: null,
  
          instantiate: function(fn, context, args, isClass) {
              if (fn.$instantiate) {
                  return fn.$instantiate.apply(fn, args);
              }
              else if (isClass) {
                  return instantiate(fn, args);
              }
              else {
                  return fn.apply(context, args);
              }
          },
  
          inject: function(injectable, context, currentValues, callArgs, isClass) {
  
              currentValues   = currentValues || {};
              callArgs        = callArgs || [];
  
              var self = this;
  
              if (isFunction(injectable)) {
  
                  if (injectable.inject) {
                      var tmp = slice.call(injectable.inject);
                      tmp.push(injectable);
                      injectable = tmp;
                  }
                  else {
                      return self.instantiate(injectable, context, callArgs, isClass);
                  }
              }
              else if (isString(injectable)) {
                  return self.resolve(injectable, currentValues);
              }
              else {
                  injectable = slice.call(injectable);
              }
  
              var values  = [],
                  fn      = injectable.pop(),
                  i, l;
  
              for (i = -1, l = injectable.length; ++i < l;
                   values.push(self.resolve(injectable[i], currentValues))) {}
  
              return Promise.all(values).then(function(values){
                  return self.instantiate(fn, context, values, isClass);
              });
          },
  
          value: function(name, value) {
              this.store[name] = {
                  type: VALUE,
                  value: value
              };
          },
  
          constant: function(name, value) {
              var store = this.store;
              if (!store[name]) {
                  store[name] = {
                      type: CONSTANT,
                      value: value
                  };
              }
          },
  
          factory: function(name, fn, context, singleton) {
  
              if (isBool(context)) {
                  singleton = context;
                  context = null;
              }
  
              this.store[name] = {
                  type: FACTORY,
                  singleton: singleton,
                  fn: fn,
                  context: context
              };
          },
  
          service: function(name, constr, singleton) {
              this.store[name] = {
                  type: SERVICE,
                  singleton: singleton,
                  fn: constr
              };
          },
  
          provider: function(name, constr) {
  
              this.store[name + "Provider"] = {
                  name: name,
                  type: PROVIDER,
                  fn: constr,
                  instance: null
              };
          },
  
          resolve: function(name, currentValues, callArgs) {
  
              var self    = this,
                  store   = self.store,
                  type,
                  item,
                  res;
  
              currentValues = currentValues || {};
              callArgs = callArgs || [];
  
              if (currentValues[name] !== undf) {
                  return currentValues[name];
              }
  
              if (item = store[name]) {
  
                  type    = item.type;
  
                  if (type === VALUE || type === CONSTANT) {
                      return item.value;
                  }
                  else if (type === FACTORY) {
                      res = self.inject(item.fn, item.context, currentValues, callArgs);
                  }
                  else if (type === SERVICE) {
                      res = self.inject(item.fn, null, currentValues, callArgs, true);
                  }
                  else if (type === PROVIDER) {
  
                      if (!item.instance) {
  
                          item.instance = Promise.resolve(
                                  self.inject(item.fn, null, currentValues)
                              )
                              .done(function(instance){
                                  item.instance = instance;
                                  store[item.name] = {
                                      type: FACTORY,
                                      fn: instance.$get,
                                      context: instance
                                  };
                              });
                      }
  
                      return item.instance;
                  }
  
                  if (item.singleton) {
                      item.type = VALUE;
                      item.value = res;
  
                      if (type === FACTORY && isThenable(res)) {
                          res.done(function(value){
                              item.value = value;
                          });
                      }
                  }
  
                  return currentValues[name] = res;
              }
              else {
  
                  if (store[name + "Provider"]) {
                      self.resolve(name + "Provider", currentValues);
                      return self.resolve(name, currentValues);
                  }
  
                  if (self === globalProvider) {
                      throw "Could not provide value for " + name;
                  }
                  else {
                      return globalProvider.resolve(name);
                  }
              }
          },
  
          destroy: function() {
  
              var self = this;
  
              self.store = null;
              self.scope = null;
          }
  
      }, true, false);
  
      Provider.global = function() {
          return globalProvider;
      };
  
      globalProvider = new Provider;
  
      return Provider;
  }();
  
  
  
  
  
  
  function resolveComponent(cmp, cfg, scope, node, args) {
  
      cfg         = cfg || {};
      args        = args || [];
  
      scope       = scope || cfg.scope; // || new Scope;
      node        = node || cfg.node;
  
      cfg.scope   = cfg.scope || scope;
      cfg.node    = cfg.node || node;
  
      var constr      = isString(cmp) ? nsGet(cmp) : cmp;
  
      if (!constr) {
          throw "Component " + cmp + " not found";
      }
  
      if (scope && constr.$isolateScope) {
          cfg.scope   = scope = scope.$newIsolated();
      }
  
      var i,
          defers      = [],
          tpl         = constr.template || cfg.template || null,
          tplUrl      = constr.templateUrl || cfg.templateUrl || null,
          app         = scope ? scope.$app : null,
          gProvider   = Provider.global(),
          injectFn    = app ? app.inject : gProvider.inject,
          injectCt    = app ? app : gProvider,
          cloak       = cfg.cloak || null,
          inject      = {
              $node: node || null,
              $scope: scope || null,
              $config: cfg || null,
              $args: args || null
          };
  
      if (constr.resolve) {
  
          for (i in constr.resolve) {
              (function(name){
                  var d = new Promise,
                      fn;
  
                  defers.push(d.done(function(value){
                      inject[name] = value;
                      cfg[name] = value;
                      args.push(value);
                  }));
  
                  fn = constr.resolve[i];
  
                  if (isFunction(fn)) {
                      d.resolve(fn(scope, node));
                  }
                  /*else if (isString(fn)) {
                      d.resolve(injectFn(fn));
                  }*/
                  else {
                      d.resolve(
                          injectFn.call(
                              injectCt, fn, null, extend({}, inject, cfg, false, false)
                          )
                      );
                  }
  
                  d.fail(function(reason){
                      if (reason instanceof Error) {
                          error(reason);
                      }
                  });
  
              }(i));
          }
      }
  
      if (tpl || tplUrl) {
  
          cfg.template = new Template({
              scope: scope,
              node: node,
              deferRendering: true,
              ownRenderer: true,
              shadow: constr.$shadow,
              tpl: tpl,
              url: tplUrl,
              animate: !!cfg.animate
          });
  
          defers.push(cfg.template.initPromise);
  
          if (node && node.firstChild) {
              data(node, "mjs-transclude", toFragment(node.childNodes));
          }
      }
  
      var p;
  
      if (defers.length) {
          p = new Promise;
  
          Promise.all(defers)
              .done(function(){
                  p.resolve(
                      injectFn.call(
                          injectCt, constr, null, extend({}, inject, cfg, false, false), args
                      )
                  );
              })
              .fail(p.reject, p)
      }
      else {
          p = Promise.resolve(
              injectFn.call(
                  injectCt, constr, null, extend({}, inject, cfg, false, false), args
              )
          );
      }
  
      if (node && p.isPending() && cloak !== null) {
          cloak !== true ? addClass(node, cloak) : node.style.visibility = "hidden";
          p.then(function() {
              cloak !== true ? removeClass(node, cloak) : node.style.visibility = "";
          });
      }
  
      if (node) {
          p.then(function(){
              removeClass(node, "mjs-cloak");
          });
      }
  
      return p;
  };
  
  
  
  
  
  (function(){
  
      var cmpAttr = function(scope, node, cmpName, parentRenderer, attr){
  
          var constr  = typeof cmpName === "string" ?
                          nsGet(cmpName, true) : cmpName,
              nodecfg = attr ? attr.config : {};
  
          if (!constr) {
              throw "Component " + cmpName + " not found";
          }
  
          var sameScope       = nodecfg.sameScope || constr.$sameScope,
              isolateScope    = nodecfg.isolateScope || constr.$isolateScope;
  
          var newScope = isolateScope ? scope.$newIsolated() : (sameScope ? scope : scope.$new());
  
          var cfg     = extend({
              scope: newScope,
              node: node,
              parentRenderer: parentRenderer,
              destroyScope: !sameScope
          }, nodecfg, false, false);
  
          resolveComponent(cmpName, cfg, newScope, node, [cfg])
              .done(function(cmp){
                  if (nodecfg.ref) {
                      scope[nodecfg.ref] = cmp;
                  }
              });
  
          return constr.$resumeRenderer || !!constr.$shadow;
      };
  
      cmpAttr.$breakScope = false;
  
      Directive.registerAttribute("cmp", 200, cmpAttr);
  
  }());
  
  
  
  var evaluate = Watchable.eval;
  
  
  
  
  
  var Queue = (function(){
  
  
  var Queue = function(cfg) {
  
      var self = this;
  
      cfg = cfg || {};
  
      self._queue = [];
      self._map = {};
      self.id = "$$" + nextUid();
  
      for (var i in cfg) {
          self[i] = cfg[i];
      }
  };
  
  
  Queue.REPLACE = 1;
  Queue.ONCE = 2;
  Queue.MULTIPLE = 3;
  Queue.ONCE_EVER = 4;
  
  
  extend(Queue.prototype, {
  
      _queue: null,
      _map: null,
      _nextRequested: false,
      _running: false,
  
      length: 0,
      id: null,
      async: true,
      auto: true,
      thenable: false,
      stack: false,
      context: null,
      mode: Queue.MULTIPLE,
  
      add: function(fn, context, args, mode, prepend, async) {
  
          var self    = this,
              qid     = self.id,
              id      = fn[qid] || nextUid(),
              item    = {
                  id: id,
                  fn: fn,
                  context: context,
                  args: args,
                  async: async
              };
  
          mode = mode || self.mode;
  
          if (mode === Queue.ONCE_EVER && fn[qid]) {
              return fn[qid];
          }
  
          fn[qid] = id;
  
          if (self._map[id]) {
              if (mode === Queue.REPLACE) {
                  self.remove(id);
              }
              else if (mode === Queue.ONCE) {
                  return id;
              }
          }
  
          self._queue[prepend ? "unshift" : "push"](item);
          self._map[id] = item;
  
          self.length = self._queue.length;
  
          if (self.auto) {
              self.next();
          }
  
          return id;
      },
  
      append: function(fn, context, args, mode, async) {
          return this.add(fn, context, args, mode, false, async);
      },
  
      prepend: function(fn, context, args, mode, async) {
          return this.add(fn, context, args, mode, true, async);
      },
  
      remove: function(id) {
          var self = this,
              queue = self._queue,
              i, l;
  
          for (i = 0, l = queue.length; i < l; i++) {
              if (queue[i].id === id) {
                  queue.splice(i, 1);
                  break;
              }
          }
          delete self._map[id];
      },
  
      isEmpty: function() {
          return this.length === 0;
      },
  
      isRunning: function() {
          return this._running;
      },
  
      next: function() {
  
          var self    = this,
              item;
  
          if (self._running) {
              self._nextRequested = true;
              return;
          }
  
          self._nextRequested = false;
  
          item = self._queue[self.stack ? "pop" : "shift"]();
          self.length = self._queue.length;
  
          if (!item) {
              return;
          }
  
          self._running = true;
  
          delete self._map[item.id];
  
          if (!self.async && !item.async) {
              try {
                  self._processResult(item.fn.apply(item.context || self.context, item.args || []));
              }
              catch (thrown) {
                  error(thrown);
                  self._finish();
                  throw thrown;
              }
          }
          else {
              var timeout = 0;
              if (isNumber(item.async)) {
                  timeout = item.async;
              }
              else if (isNumber(self.async)) {
                  timeout = self.async;
              }
  
              var fn = function(){
                  try {
                      self._processResult(item.fn.apply(item.context || self.context, item.args || []));
                  }
                  catch (thrown) {
                      error(thrown);
                      self._finish();
                      throw thrown;
                  }
              };
  
              if (item.async === "raf" || (!item.async && self.async === "raf")) {
                  raf(fn);
              }
              else {
                  async(fn, null, null, timeout);
              }
          }
      },
  
      _processResult: function(res) {
          var self = this;
          if (self.thenable && isThenable(res)) {
              res.then(function(){self._finish()}, function(){self._finish()});
          }
          else {
              self._finish();
          }
          return res;
      },
  
      _finish: function() {
          var self = this;
          self._running = false;
          if (self.auto || self._nextRequested) {
              self.next();
          }
      },
  
      destroy: function() {
  
          var self = this;
  
          self._queue = null;
          self._map = null;
          self.context = null;
          self._nextRequested = false;
          self._running = false;
          self.next = emptyFn;
  
      }
  }, true, false);
  
  return Queue;
  }());
  
  
  
  
  
  
  
  var ListRenderer = defineClass({
  
      $class: "ListRenderer",
  
      id: null,
  
      cfg: null,
      model: null,
      itemName: null,
      tpl: null,
      renderers: null,
      parentEl: null,
      prevEl: null,
      nextEl: null,
      trackBy: null,
      trackByWatcher: null,
      animateMove: false,
      animate: false,
      trackByFn: null,
      griDelegate: null,
      tagMode: false,
  
      queue: null,
  
      buffered: false,
      bufferPlugin: null,
  
      $constructor: function(scope, node, expr, parentRenderer, attr) {
  
          var self    = this,
              cfg     = attr ? attr.config : {};
  
          self.cfg            = cfg;
          self.scope          = scope;
  
          self.tagMode        = node.nodeName.toLowerCase() === "mjs-each";
          self.animateMove    = !self.tagMode && !cfg['buffered'] &&
                                  cfg["animateMove"] && animate.cssAnimationSupported();
          self.animate        = !self.tagMode && !cfg['buffered'] && cfg["animate"];
          self.id             = cfg['id'] || nextUid();
  
          if (self.animate) {
              self.$plugins.push(cfg['animatePlugin'] || "plugin.ListAnimated");
          }
  
          if (cfg['observable']) {
              self.$plugins.push(cfg['observable'] || "plugin.Observable");
          }
  
          if (self.tagMode) {
              cfg['buffered'] = false;
          }
  
          if (cfg['buffered']) {
              self.buffered = true;
              self.$plugins.push(cfg['buffered'] || "plugin.ListBuffered");
          }
  
          if (cfg['plugin']) {
              self.$plugins.push(cfg['plugin']);
          }
  
          if (cfg['trackby'] && cfg['trackby'] === 'false') {
              self.trackBy = false;
          }
      },
  
      $init: function(scope, node, expr, parentRenderer, attr) {
  
          var self = this;
  
          if (self.tagMode) {
              expr = getAttr(node, "value");
          }
  
          self.parseExpr(expr);
  
          self.tpl        = self.tagMode ? toFragment(node.childNodes) : node;
          self.renderers  = [];
  
          var cmts = Directive.commentHolders(node, self.$class + "-" + self.id);
  
          self.prevEl     = cmts[0];
          self.nextEl     = cmts[1];
          self.parentEl   = node.parentNode;
          self.node       = null; //node;
  
          self.queue      = new Queue({
              async: false, auto: true, thenable: true,
              stack: false, context: self, mode: Queue.ONCE
          });
  
          self.parentEl.removeChild(node);
  
          self.afterInit(scope, node, expr, parentRenderer, attr);
  
          self.queue.add(self.render, self, [toArray(self.watcher.getLastResult())]);
      },
  
      afterInit: function(scope, node) {
  
          var self        = this,
              cfg         = self.cfg;
  
          self.watcher    = createWatchable(scope, self.model, self.onChange, self, {filterLookup: filterLookup});
          self.trackBy    = cfg.trackby; // lowercase from attributes
          
          if (self.trackBy !== false) {
              if (self.trackBy && self.trackBy !== '$') {
                  self.trackByWatcher = createWatchable(scope, self.trackBy, self.onChangeTrackBy, self, {filterLookup: filterLookup});
              }
              else if (self.trackBy !== '$' && !self.watcher.hasInputPipes()) {
                  self.trackBy    = '$$'+self.watcher.id;
              }
          }
  
          self.griDelegate = bind(self.scopeGetRawIndex, self);
      },
  
      trigger: emptyFn,
  
      /*
       * <!-- render and re-render
       */
  
      render: function(list) {
  
          var self        = this,
              renderers   = self.renderers,
              tpl         = self.tpl,
              i, len;
  
          for (i = 0, len = list.length; i < len; i++) {
              renderers.push(self.createItem(tpl.cloneNode(true), list, i));
          }
  
          self.doRender();
      },
  
      doRender: function() {
  
          var self        = this,
              fragment    = window.document.createDocumentFragment(),
              renderers   = self.renderers,
              tm          = self.tagMode,
              i, len;
  
          for (i = 0, len = renderers.length; i < len; i++) {
  
              if (!renderers[i].hidden) {
                  if (tm) {
                      fragment.appendChild(toFragment(renderers[i].el));
                  }
                  else {
                      fragment.appendChild(renderers[i].el);
                  }
                  renderers[i].attached = true;
              }
          }
  
          self.parentEl.insertBefore(fragment, self.nextEl);
          self.doUpdate();
  
          self.trigger("render", self);
      },
  
      doUpdate: function(start, end, action, renderOnly) {
  
          var self        = this,
              renderers   = self.renderers,
              index       = start || 0,
              cnt         = renderers.length,
              x           = end || cnt - 1,
              list        = self.watcher.getLastResult(),
              trackByFn   = self.getTrackByFunction();
  
          if (x > cnt - 1) {
              x = cnt - 1;
          }
  
          for (; index <= x; index++) {
  
              if (action && renderers[index].action !== action) {
                  continue;
              }
  
              self.renderItem(index, renderers, list, trackByFn, renderOnly);
          }
      },
  
      renderItem: function(index, rs, list, trackByFn, renderOnly) {
  
          var self = this;
  
          list = list || self.watcher.getLastResult();
          rs = rs || self.renderers;
          trackByFn = trackByFn || self.getTrackByFunction();
  
          var item        = rs[index],
              scope       = item.scope,
              last        = rs.length - 1,
              even        = !(index % 2);
  
          if (renderOnly && item.rendered) {
              return;
          }
  
          scope.$index    = index;
          scope.$first    = index === 0;
          scope.$last     = index === last;
          scope.$even     = even;
          scope.$odd      = !even;
          scope.$trackId  = trackByFn(list[index]);
          scope.$getRawIndex = self.griDelegate;
  
          if (!item.renderer) {
              item.renderer  = new Renderer(item.el, scope);
              item.renderer.process();
              item.rendered = true;
          }
          else {
              scope.$check();
          }
      },
  
  
      createItem: function(el, list, index) {
  
          var self        = this,
              iname       = self.itemName,
              itemScope   = self.scope.$new(),
              tm          = self.tagMode;
  
          itemScope.$on("changed", self.scope.$check, self.scope);
  
          itemScope[iname]    = self.getListItem(list, index);
          el = tm ? toArray(el.childNodes) : el;
  
          return {
              index: index,
              action: "enter",
              el: el,
              firstEl: tm ? el[0] : el,
              lastEl: tm ? el[el.length - 1] : el,
              scope: itemScope,
              attached: false,
              rendered: false,
              hidden: false
          };
      },
  
      /*
       * render and re-render -->
       */
  
      /*
       * <!-- reflect changes
       */
  
      onChange: function(current, prev) {
          var self = this;
          self.queue.prepend(self.applyChanges, self, [prev], Queue.REPLACE);
      },
  
      applyChanges: function(prevList) {
  
          var self        = this,
              renderers   = self.renderers,
              tpl         = self.tpl,
              index       = 0,
              list        = toArray(self.watcher.getLastResult()),
              updateStart = null,
              animateMove = self.animateMove,
              newrs       = [],
              iname       = self.itemName,
              origrs      = renderers.slice(),
              doesMove    = false,
              prevr,
              prevrInx,
              i, len,
              r,
              action;
  
          if (self.trackBy === false) {
              renderers = self.renderers.slice();
              updateStart = 0;
              doesMove = false;
              for (i = 0, len = list.length; i < len; i++) {
                  r = self.createItem(tpl.cloneNode(true), list, i);
                  newrs.push(r);
              }
          }
          else {
  
              var prs  = self.watcher.getMovePrescription(prevList, self.getTrackByFunction(), list);
  
              // redefine renderers
              for (i = 0, len = prs.length; i < len; i++) {
  
                  action = prs[i];
  
                  if (isNumber(action)) {
                      prevrInx    = action;
                      prevr       = renderers[prevrInx];
  
                      if (prevrInx !== index && isNull(updateStart)) {
                          updateStart = i;
                      }
  
                      prevr.action = "move";
                      prevr.scope[iname] = self.getListItem(list, i);
                      doesMove = animateMove;
  
                      newrs.push(prevr);
                      renderers[prevrInx] = null;
                      index++;
                  }
                  else {
                      if (isNull(updateStart)) {
                          updateStart = i;
                      }
                      r = self.createItem(tpl.cloneNode(true), list, i);
                      newrs.push(r);
                      // add new elements to old renderers
                      // so that we could correctly determine positions
                  }
              }
          }
  
          self.renderers  = newrs;
  
          self.reflectChanges({
              oldRenderers:   renderers,
              updateStart:    updateStart,
              newRenderers:   newrs,
              origRenderers:  origrs,
              doesMove:       doesMove
          });
      },
  
  
      reflectChanges: function(vars) {
          var self = this;
          self.applyDomPositions(vars.oldRenderers);
          self.doUpdate(vars.updateStart || 0);
          self.removeOldElements(vars.oldRenderers);
          self.trigger("change", self);
      },
  
  
  
      removeOldElements: function(rs) {
          var i, len, r,
              j, jl,
              self    = this,
              parent  = self.parentEl;
  
          for (i = 0, len = rs.length; i < len; i++) {
              r = rs[i];
              if (r && r.attached) {
                  r.attached = false;
                  if (!self.tagMode && r.el.parentNode) {
                      r.el.parentNode.removeChild(r.el);
                  }
                  else {
                      for (j = 0, jl = r.el.length; j < jl; j++) {
                          if (r.el[j].parentNode) {
                              r.el[j].parentNode.removeChild(r.el[j]);
                          }
                      }
                  }
              }
              if (r && r.scope) {
                  r.scope.$destroy();
              }
          }
      },
  
  
      applyDomPositions: function(oldrs) {
  
          var self        = this,
              rs          = self.renderers,
              parent      = self.parentEl,
              prevEl      = self.prevEl,
              tm          = self.tagMode,
              nc          = self.nextEl,
              next,
              i, l, el, r,
              j;
  
          /*if (nc && nc.parentNode !== parent) {
              nc = null;
          }
          //if (!nc && prevEl && prevEl.parentNode === parent) {
          //    nc = prevEl.nextSibling;
          //}*/
  
          for (i = 0, l = rs.length; i < l; i++) {
              r = rs[i];
              el = r.el;
              next = null;
  
              if (r.hidden) {
                  if (el.parentNode) {
                      if (tm) {
                          el.parentNode.removeChild(toFragment(el));
                      }
                      else {
                          el.parentNode.removeChild(el);
                      }
                      r.attached = false;
                  }
                  continue;
              }
  
              for (j = Math.max(i - 1, 0); j >= 0; j--) {
                  if (rs[j].attached) {
                      next = rs[j].lastEl.nextSibling;
                      break;
                      //if (next && next.parentNode === parent) {
                      //    break;
                      //}
                  }
              }
  
              if (!next) {
                  next = nc;
              }
  
              // positions of some elements have changed
              /*if (oldrs) {
                  // oldrs looks like [obj, obj, null, obj] where nulls are instead
                  // of items that were moved somewhere else
                  if (oldrs && oldrs[i]) {
                      // so if item is found in oldrs[i] it means it hasn't moved
                      next = oldrs[i].lastEl.nextSibling;
                  }
                  // if oldrs is shorter than rs, then we put all following items
                  // at the end
                  else if (oldrs && oldrs.length && oldrs.length <= i) {
                      next = self.nextEl && self.nextEl.parentNode === parent ?
                             self.nextEl : null;
                  }
                  // if oldrs[i] === null or it is empty
                  // we put the first item before nextEl and all all following
                  // items after first one
                  else {
                      next = i > 0 ? (rs[i - 1].lastEl.nextSibling || nc) : nc;
                  }
              }
              // items were hidden/shown but positions haven't changed
              else {
                  for (j = Math.max(i - 1, 0); j < l; j++) {
                      if (j === i) {
                          continue;
                      }
                      if (rs[j].attached && rs[j].lastEl.parentNode === parent) {
                          next = j < i ? rs[j].lastEl.nextSibling : rs[j].firstEl;
                          if (next.parentNode === parent) {
                              break;
                          }
                      }
                  }
                  if (!next) {
                      next = nc;
                  }
              }
  
              if (next && next.parentNode !== parent) {
                  next = null;
              }*/
  
              if (r.firstEl !== next) {
                  if (next && r.lastEl.nextSibling !== next) {
                      parent.insertBefore(tm ? toFragment(el) : el, next);
                  }
                  else if (!next) {
                      parent.appendChild(tm ? toFragment(el) : el);
                  }
              }
  
              r.attached = true;
          }
      },
  
      /*
       * reflect changes -->
       */
  
  
      /*
       * <!-- configurable item functions
       */
  
  
      getListItem: function(list, index) {
          return list[index];
      },
  
      onChangeTrackBy: function(val) {
          this.trackByFn = null;
          this.trackBy = val;
      },
  
      getTrackByFunction: function() {
  
          var self = this,
              trackBy;
  
          if (!self.trackByFn) {
  
              trackBy = self.trackBy;
  
              if (!trackBy || trackBy === '$') {
                  self.trackByFn = function(item) {
                      return isPrimitive(item) ? item : undf;
                  };
              }
              else if (isFunction(trackBy)) {
                  self.trackByFn = trackBy;
              }
              else {
                  self.trackByFn = function(item){
                      return item && !isPrimitive(item) ? item[trackBy] : undf;
                  };
              }
          }
  
          return self.trackByFn;
      },
  
  
      scopeGetRawIndex: function(id) {
  
          if (id === undf) {
              return -1;
          }
  
          var self        = this,
              list        = self.watcher.getUnfilteredValue(),
              trackByFn   = self.getTrackByFunction(),
              i, l;
  
          for (i = 0, l = list.length; i < l; i++) {
              if (trackByFn(list[i]) === id) {
                  return i;
              }
          }
  
          return -1;
      },
  
      /*
       * configurable item functions -->
       */
  
  
  
      parseExpr: function(expr) {
  
          var tmp = expr.split(" "),
              i, len,
              model, name,
              row;
  
          for (i = 0, len = tmp.length; i < len; i++) {
  
              row = tmp[i];
  
              if (row === "" || row === "in") {
                  continue;
              }
  
              if (!name) {
                  name = row;
              }
              else {
                  model = tmp.slice(i).join(" ");
                  break;
              }
          }
  
          this.model = model;
          this.itemName = name || "item";
      },
  
  
      destroy: function() {
  
          var self        = this,
              renderers   = self.renderers,
              i, len;
  
          for (i = 0, len = renderers.length; i < len; i++) {
              if (renderers[i].renderer && !renderers[i].renderer.$destroyed) {
                  renderers[i].renderer.$destroy();
              }
          }
  
          if (self.trackByWatcher) {
              self.trackByWatcher.unsubscribeAndDestroy();
          }
  
          self.queue.destroy();
  
          if (self.watcher) {
              self.watcher.unsubscribeAndDestroy(self.onChange, self);
          }
      }
  
  }, {
      $stopRenderer: true,
      $registerBy: "id"
  });
  
  
  
  
  
  
  
  (function(){
  
      var types = [];
  
      function detectModelType(expr, scope) {
          var tmp = expr.split(" in "),
              model = tmp.length === 1 ? expr : tmp[1],
              obj = evaluate(model, scope, {filterLookup: filterLookup}),
              i = 0,
              l = types.length;
  
          for (; i < l; i++) {
              if (obj instanceof types[i][0]) {
                  return types[i][1]
              }
          }
  
          return null;
      }
  
      var eachDirective = function eachDirective(scope, node, expr, parentRenderer, attr) {
          var tagMode = node.nodeName.toLowerCase() === "mjs-each";
          if (tagMode) {
              expr = getAttr(node, "value");
          }
          var handler = detectModelType(expr, scope) || ListRenderer;
          return new handler(scope, node, expr, parentRenderer, attr);
      };
  
  
      eachDirective.registerType = function(objectClass, handlerClass) {
          types.push([objectClass, handlerClass]);
      };
  
      eachDirective.$stopRenderer = true;
      eachDirective.$registerBy = "id";
  
      eachDirective.registerType(Array, ListRenderer);
  
      Directive.registerAttribute("each", 100, eachDirective);
      Directive.registerTag("each", eachDirective);
  
  }());
  
  
  
  
  var getStyle = function(node, prop, numeric) {
  
      var style, val;
  
      if (window.getComputedStyle) {
  
          if (node === window) {
              return prop? (numeric ? 0 : null) : {};
          }
          style = getComputedStyle(node, null);
          val = prop ? style[prop] : style;
      }
      else {
          style = node.currentStyle || node.style || {};
          val = prop ? style[prop] : style;
      }
  
      return numeric ? parseFloat(val) || 0 : val;
  
  };
  
  
  
  var boxSizingReliable = function() {
  
      var boxSizingReliableVal;
  
      var computePixelPositionAndBoxSizingReliable = function() {
  
          var doc = window.document,
              container = doc.createElement("div"),
              div = doc.createElement("div"),
              body = doc.body;
  
          if (!div.style || !window.getComputedStyle) {
              return false;
          }
  
          container.style.cssText = "border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;" +
                                    "position:absolute";
          container.appendChild(div);
  
          div.style.cssText =
              // Support: Firefox<29, Android 2.3
              // Vendor-prefix box-sizing
          "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
          "box-sizing:border-box;display:block;margin-top:1%;top:1%;" +
          "border:1px;padding:1px;width:4px;position:absolute";
          div.innerHTML = "";
          body.appendChild(container);
  
          var divStyle = window.getComputedStyle(div, null),
              ret = divStyle.width === "4px";
  
          body.removeChild(container);
  
          return ret;
      };
  
      return function boxSizingReliable() {
          if (boxSizingReliableVal === undf) {
              boxSizingReliableVal = computePixelPositionAndBoxSizingReliable();
          }
  
          return boxSizingReliableVal;
      };
  }();
  
  // from jQuery
  
  
  
  var getDimensions = function(type, name) {
  
      var rnumnonpx = new RegExp( "^([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$", "i"),
          cssExpand = [ "Top", "Right", "Bottom", "Left" ],
          defaultExtra = !type ? "content" : (type === "inner" ? "padding" : "");
  
      var augmentWidthOrHeight = function(elem, name, extra, isBorderBox, styles) {
          var i = extra === (isBorderBox ? "border" : "content") ?
                  // If we already have the right measurement, avoid augmentation
                  4 :
                  // Otherwise initialize for horizontal or vertical properties
                  name === "width" ? 1 : 0,
  
              val = 0;
  
          for (; i < 4; i += 2) {
              // Both box models exclude margin, so add it if we want it
              if (extra === "margin") {
                  val += parseFloat(styles[extra + cssExpand[i]]);
              }
  
              if (isBorderBox) {
                  // border-box includes padding, so remove it if we want content
                  if (extra === "content") {
                      val -= parseFloat(styles["padding" + cssExpand[i]]);
                  }
  
                  // At this point, extra isn't border nor margin, so remove border
                  if (extra !== "margin") {
                      val -= parseFloat(styles["border" + cssExpand[i] + "Width"]);
                  }
              } else {
                  // At this point, extra isn't content, so add padding
                  val += parseFloat(styles["padding" + cssExpand[i]]);
  
                  // At this point, extra isn't content nor padding, so add border
                  if (extra !== "padding") {
                      val += parseFloat(styles["border" + cssExpand[i] + "Width"]);
                  }
              }
          }
  
          return val;
      };
  
      var getWidthOrHeight = function(elem, name, extra, styles) {
  
          // Start with offset property, which is equivalent to the border-box value
          var valueIsBorderBox = true,
              val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
              isBorderBox = styles["boxSizing"] === "border-box";
  
          // Some non-html elements return undefined for offsetWidth, so check for null/undefined
          // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
          // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
          if ( val <= 0 || val == null ) {
              val = elem.style[name];
  
              // Computed unit is not pixels. Stop here and return.
              if (rnumnonpx.test(val)) {
                  return val;
              }
  
              // Check for style in case a browser which returns unreliable values
              // for getComputedStyle silently falls back to the reliable elem.style
              valueIsBorderBox = isBorderBox &&
                                 (boxSizingReliable() || val === elem.style[name]);
  
              // Normalize "", auto, and prepare for extra
              val = parseFloat(val) || 0;
          }
  
          // Use the active box-sizing model to add/subtract irrelevant styles
          return val +
                   augmentWidthOrHeight(
                       elem,
                       name,
                       extra || (isBorderBox ? "border" : "content"),
                       valueIsBorderBox,
                       styles
                   );
      };
  
  
      return function getDimensions(elem, margin) {
  
          if (elem === window) {
              return elem.document.documentElement["client" + name];
          }
  
          // Get document width or height
          if (elem.nodeType === 9) {
              var doc = elem.documentElement;
  
              // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
              // whichever is greatest
              return Math.max(
                  elem.body["scroll" + name], doc["scroll" + name],
                  elem.body["offset" + name], doc["offset" + name],
                  doc["client" + name]
              );
          }
  
          return getWidthOrHeight(
              elem,
              name.toLowerCase(),
              defaultExtra || (margin === true ? "margin" : "border"),
              getStyle(elem)
          );
      };
  
  };
  
  
  
  var getWidth = getDimensions("", "Width");
  
  
  var getHeight = getDimensions("", "Height");
  
  
  
  var getScrollTopOrLeft = function(vertical) {
  
      var defaultST,
          wProp = vertical ? "pageYOffset" : "pageXOffset",
          sProp = vertical ? "scrollTop" : "scrollLeft",
          doc = window.document,
          body = doc.body,
          html = doc.documentElement;
  
      var ret = function(scroll, allowNegative) {
          if (scroll < 0 && allowNegative === false) {
              return 0;
          }
          return scroll;
      };
  
      if(window[wProp] !== undf) {
          //most browsers except IE before #9
          defaultST = function(){
              return window[wProp];
          };
      }
      else{
          if (html.clientHeight) {
              defaultST = function() {
                  return html[sProp];
              };
          }
          else {
              defaultST = function() {
                  return body[sProp];
              };
          }
      }
  
      return function(node, allowNegative) {
          if (!node || node === window) {
              return ret(defaultST(), allowNegative);
          }
          else if (node && node.nodeType == 1 &&
              node !== body && node !== html) {
              return ret(node[sProp], allowNegative);
          }
          else {
              return ret(defaultST(), allowNegative);
          }
      }
  
  };
  
  
  
  var getScrollTop = getScrollTopOrLeft(true);
  
  
  
  var getScrollLeft = getScrollTopOrLeft(false);
  
  
  
  
  var EventBuffer = function(){
  
      var bufferKey = function(event, interval) {
          return '$$' + event + "_" + interval;
      };
  
      var EventBuffer = defineClass({
  
          observable: null,
          handlerDelegate: null,
          triggerDelegate: null,
          watchers: null,
          breaks: null,
          running: false,
          lastEvent: null,
          currentEvent: null,
          interval: null,
          id: null,
  
          $init: function(node, event, interval) {
  
              var self = this,
                  key = bufferKey(event, interval);
  
              if (node[key]) {
                  return node[key];
              }
  
              node[key] = self;
  
              self.id = key;
              self.breaks = {};
              self.watchers = {};
              self.node = node;
              self.event = event;
              self.observable = new Observable;
              self.interval = interval || 0;
              self.handlerDelegate = bind(self.handler, self);
              self.triggerDelegate = bind(self.trigger, self);
  
              self.up();
          },
  
          handler: function(e) {
              var self = this;
              if (self.running) {
                  if (e) {
                      self.lastEvent = e;
                  }
              }
              else {
                  self.next(e);
              }
          },
  
          next: function(e) {
  
              var self = this,
                  itv = self.interval;
  
              e = e || self.lastEvent;
  
              if (!e) {
                  return;
              }
  
              self.lastEvent = null;
              self.running = true;
              self.currentEvent = e;
  
              if (itv === "raf") {
                  raf(self.triggerDelegate);
              }
              else {
                  setTimeout(self.triggerDelegate, itv);
              }
          },
  
          watchWidth: function() {
              this.addWatcher("width", getWidth);
          },
  
          watchHeight: function() {
              this.addWatcher("width", getHeight);
          },
  
          watchScrollTop: function() {
              this.addWatcher("scrollTop", getScrollTop);
          },
  
          watchScrollLeft: function() {
              this.addWatcher("scrollLeft", getScrollLeft);
          },
  
          addWatcher: function(name, fn, context) {
              if (!this.watchers[name]) {
                  this.watchers[name] = {
                      fn:      fn,
                      context: context,
                      prev:    null,
                      current: parseInt(fn.call(context, this.node), 10)
                  };
              }
          },
  
          removeWatcher: function(name) {
              delete this.watchers[name];
          },
  
          breakFilter: function(l, args, event) {
  
              if (!this.watchers[event.watcher]) {
                  return false;
              }
  
              var self        = this,
                  breakValue  = l.breakValue,
                  luft        = l.breakLuft || 0,
                  lowLuft     = l.breakLowLuft || luft,
                  highLuft    = l.breakHighLuft || luft,
                  lowBreak    = breakValue - lowLuft,
                  highBreak   = breakValue + highLuft,
                  w           = self.watchers[event.watcher],
                  current     = w.current,
                  prev        = w.prev,
                  min         = Math.min(prev, current),
                  max         = Math.max(prev, current);
  
              if (breakValue === "!=") {
                  return prev != current;
              }
  
              args[0].breakPosition = current < lowBreak ? -1 :  (current >= highBreak ? 1 : 0);
  
              return (min <= lowBreak && lowBreak <= max) ||
                      (min <= highBreak && highBreak <= max);
          },
  
          onBreak: function(watcher, breakValue, fn, context, options) {
              var self = this,
                  name = watcher + "_" + breakValue;
  
              options = options || {};
              options.breakValue = breakValue;
  
              if (!self.breaks[name]) {
                  self.breaks[name] = self.observable.createEvent(name, {
                      watcher: watcher,
                      triggerFilter: self.breakFilter,
                      filterContext: self
                  });
              }
  
              self.breaks[name].on(fn, context, options);
          },
  
          unBreak: function(watcher, breakValue, fn, context, destroy) {
              var self = this,
                  name = watcher + "_" + breakValue;
              if (self.breaks[name]) {
                  self.breaks[name].un(fn, context);
                  if (!self.breaks[name].hasListener()) {
                      self.observable.destroyEvent(name);
                      self.breaks[name] = null;
                      delete self.breaks[name];
                  }
              }
              if (destroy) {
                  self.destroyIfIdle();
              }
          },
  
          on: function(fn, context, options) {
              this.observable.on(this.event, fn, context, options);
          },
  
          un: function(fn, context, destroy) {
              var self = this;
              self.observable.un(self.event, fn, context);
              if (destroy) {
                  self.destroyIfIdle();
              }
          },
  
          trigger: function() {
              var self = this,
                  e = self.currentEvent,
                  ws = self.watchers,
                  bs = self.breaks,
                  node = self.node,
                  w, b;
  
              self.observable.trigger(self.event, e);
  
              for (w in ws) {
                  ws[w].prev = ws[w].current;
                  ws[w].current = parseInt(ws[w].fn.call(ws[w].context, node, e), 10);
              }
  
              for (b in bs) {
                  bs[b].trigger(e);
              }
  
              self.running = false;
              self.currentEvent = null;
  
              self.next();
          },
  
          up: function() {
              var self = this;
              addListener(self.node, self.event, self.handlerDelegate);
          },
  
          down: function() {
              var self = this;
              removeListener(self.node, self.event, self.handlerDelegate);
          },
  
          destroyIfIdle: function() {
              if (this.observable && !this.observable.hasListener()) {
                  this.$destroy();
                  return true;
              }
          },
  
          destroy: function() {
  
              var self = this;
  
              delete self.node[self.id];
  
              self.down();
              self.observable.destroy();
  
          }
  
      }, {
          get: function(node, event, interval) {
              var key = bufferKey(event, interval);
  
              if (node[key]) {
                  return node[key];
              }
  
              return node[key] = new EventBuffer(node, event, interval);
          }
      });
  
      return EventBuffer;
  
  }();
  
  
  
  
  
  var EventHandler = defineClass({
  
      cfg: null,
      scope: null,
      node: null,
      listeners: null,
      event: null,
      buffers: null,
      updateRoot: false,
      prevEvent: null,
  
      $init: function(scope, node, cfg, event, defaults) {
  
          var self = this;
  
          self.event = event;
          self.prevEvent = {};
  
          defaults = defaults || {};
  
          cfg = cfg || {};
  
          if (typeof cfg === "string") {
  
              self.updateRoot = cfg.indexOf('$root') + cfg.indexOf('$parent') !== -2;
  
              var fc = cfg.substr(0,1);
  
              if (fc === '{') {
                  self.watcher = createWatchable(scope, cfg, self.onConfigChange, self);
                  cfg = extend({}, self.watcher.getLastResult(), true, true);
              }
              else if (fc === '=') {
                  cfg = cfg.substr(1);
                  self.watcher = createWatchable(scope, cfg, self.onConfigChange, self);
                  cfg = extend({}, self.watcher.getLastResult(), true, true);
              }
              else {
                  var handler = createGetter(cfg);
                  cfg = {
                      handler: handler
                  };
              }
          }
  
          self.buffers    = {};
          self.listeners  = [];
          self.scope      = scope;
          self.node       = node;
  
          self.prepareConfig(cfg, defaults);
  
          self.up();
      },
  
      prepareConfig: function(cfg, defaults) {
  
          var tmp,
              event = this.event;
  
          extend(cfg, defaults, false, false);
  
          if (cfg.handler && typeof cfg.handler === "string") {
              cfg.handler = createGetter(cfg.handler);
          }
  
          if (cfg.event) {
              tmp = {};
              var events = cfg.event.split(","),
                  i, l;
  
              for (i = 0, l = events.length; i < l; i++) {
                  tmp[trim(events[i])] = cfg;
              }
  
              cfg = tmp;
          }
          else if (event) {
              tmp = {};
              tmp[event] = cfg;
              cfg = tmp;
          }
  
          this.cfg = cfg;
      },
  
      onConfigChange: function(val) {
          var self = this;
          val = extend({}, val, true, true);
          self.down();
          self.prepareConfig(val);
          self.up();
      },
  
      createHandler: function(cfg, scope) {
  
          var self        = this,
              updateRoot  = self.updateRoot;
  
          var handler = function(e){
  
              if (self.$destroyed || self.$destroying) {
                  return;
              }
  
              var keyCode,
                  preventDefault = false,
                  returnValue = undf,
                  stopPropagation = false,
                  res;
  
              cfg.preventDefault !== undf && (preventDefault = cfg.preventDefault);
              cfg.stopPropagation !== undf && (stopPropagation = cfg.stopPropagation);
              cfg.returnValue !== undf && (returnValue = cfg.returnValue);
              cfg.keyCode !== undf && (keyCode = cfg.keyCode);
  
              e = normalizeEvent(e || window.event);
  
              if (keyCode) {
                  if (typeof keyCode === "number" && keyCode !== e.keyCode) {
                      return null;
                  }
                  else if (keyCode.indexOf(e.keyCode) === -1) {
                      return null;
                  }
              }
  
              scope.$event = e;
              scope.$eventNode = self.node;
              scope.$prevEvent = self.prevEvent[e.type];
  
              if (cfg.handler) {
                  res = cfg.handler.call(cfg.context || null, scope);
  
                  if (res && isPlainObject(res)) {
                      res.preventDefault !== undf && (preventDefault = res.preventDefault);
                      res.stopPropagation !== undf && (stopPropagation = res.stopPropagation);
                      res.returnValue !== undf && (returnValue = res.returnValue);
                  }
              }
  
              stopPropagation && e.stopPropagation();
              preventDefault && e.preventDefault();
  
              if (self.$destroyed || self.$destroying) {
                  return returnValue !== undf ? returnValue : undf;
              }
  
              scope.$event = null;
              scope.$eventNode = null;
  
              self.prevEvent[e.type] = e;
  
              updateRoot ? scope.$root.$check() : scope.$check();
  
              if (returnValue !== undf) {
                  return returnValue;
              }
          };
  
          if (cfg.async) {
              return function(e) {
                  async(handler, null, [e], 
                          typeof cfg.async == "number" ? cfg.async : null);
              };
          }
          else {
              return handler;
          }
      },
  
      up: function() {
  
          var self    = this,
              allCfg  = self.cfg,
              ls      = self.listeners,
              bs      = self.buffers,
              node    = self.node,
              scope   = self.scope,
              cfg,
              buffer,
              handler,
              event;
  
          for (event in allCfg) {
              cfg = allCfg[event];
              buffer = cfg.buffer;
  
              if (cfg['if'] === undf || cfg['if']) {
                  handler = self.createHandler(cfg, scope);
                  ls.push([event, handler]);
  
                  if (buffer) {
                      if (!bs[event]) {
                          bs[event] = EventBuffer.get(node, event, buffer);
                          bs[event].on(handler);
                      }
                  }
                  else {
                      addListener(node, event, handler);
                  }
              }
          }
      },
  
      down: function() {
  
          var self    = this,
              ls      = self.listeners,
              bs      = self.buffers,
              node    = self.node,
              buffer  = self.cfg.buffer,
              event,
              handler,
              i, l;
  
  
          for (i = 0, l = ls.length; i < l; i++) {
              event = ls[i][0];
              handler = ls[i][1];
              if (buffer) {
                  bs[event].un(handler);
                  if (bs[event].destroyIfIdle() === true) {
                      delete bs[event];
                  }
              }
              else {
                  removeListener(node, event, handler);
              }
          }
  
          self.listeners  = [];
      },
  
      destroy: function() {
          var self = this;
          self.down();
          if (self.watcher) {
              self.watcher.unsubscribeAndDestroy(self.onConfigChange, self);
          }
      }
  
  });
  
  
  
  (function(){
  
      var events = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover',
                    'mouseout', 'mousemove', 'keydown', 'keyup', 'keypress',
                    'change',
                    'focus', 'blur', 'copy', 'cut', 'paste', 'load', 'mousewheel',
                    'touchstart', 'touchend', 'touchcancel', 'touchleave', 'touchmove'],
          i, len;
  
      for (i = 0, len = events.length; i < len; i++) {
  
          (function(name){
  
              Directive.registerAttribute(name, 1000,
                  function(scope, node, expr, renderer, attr){
  
                  var cfg = attr && attr.config ? extend({}, attr.config) : null,
                      keep = false,
                      k;
  
                  if (cfg) {
                      for (k in cfg) {
                          if (cfg.hasOwnProperty(k)) {
                              keep = true;
                              break;
                          }
                      }
                      if (cfg.preventDefault) {
                          cfg.preventDefault = createGetter(cfg.preventDefault)(scope);
                      }
                      if (cfg.stopPropagation) {
                          cfg.stopPropagation = createGetter(cfg.stopPropagation)(scope);
                      }
                      if (cfg.async) {
                          cfg.async = createGetter(cfg.async)(scope);
                      }
                  }
  
                  if (!keep) {
                      cfg = null;
                  }
  
                  if (cfg) {
                      cfg.handler = expr;
                      expr = cfg;
                  }
  
                  var eh = new EventHandler(scope, node, expr, name, {
                      preventDefault: true
                  });
  
                  return function(){
                      eh.$destroy();
                      eh = null;
                  };
              });
  
          }(events[i]));
      }
  
      Directive.registerAttribute("submit", 1000, function(scope, node, expr){
  
          var fn = createFunc(expr),
              updateRoot = expr.indexOf('$root') + expr.indexOf('$parent') !== -2,
              handler = function(){
                  fn(scope);
                  updateRoot ? scope.$root.$check() : scope.$check();
              };
  
          Input.get(node).onKey(13, handler);
  
          return function() {
              Input.get(node).unKey(13, handler);
              handler = null;
              fn = null;
          };
      });
  
      events = null;
  
  }());
  
  
  
  
  
  
  Directive.registerAttribute("show", 500, defineClass({
  
      $class: "Directive.attr.Show",
      $extends: Directive,
  
      animate: false,
      initial: true,
      display: "",
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self    = this,
              cfg     = attr ? attr.config : {};
  
          self.display = cfg.display || "";
          self.animate = !!cfg.animate;
  
          self.$super(scope, node, expr, renderer, attr);
      },
  
      runAnimation: function(show) {
  
          var self    = this,
              style   = self.node.style,
              done    = function() {
                  if (!show) {
                      style.display = "none";
                  }
                  else {
                      style.display = self.display;
                  }
              };
  
          self.initial || !self.animate ? done() : animate(
              self.node,
              show ? "show" : "hide",
              function() {
                  if (show) {
                      var p = new Promise;
                      raf(function(){
                          style.display = self.display;
                          p.resolve();
                      });
                      return p;
                  }
              })
              .done(done);
      },
  
      onChange: function(val) {
          var self    = this;
          self.runAnimation(val);
          self.initial = false;
          self.$super(val);
      }
  }));
  
  
  
  
  
  
  
  Directive.registerAttribute("hide", 500, defineClass({
  
      $class: "Directive.attr.Hide",
      $extends: "Directive.attr.Show",
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self    = this;
  
          self.$super(scope, node, expr, renderer, attr);
          self.display = null;
      },
  
      onChange: function() {
          var self    = this,
              val     = self.watcher.getLastResult();
  
          self.runAnimation(!val);
          self.initial = false;
  
          self.saveStateOnChange(val);
      }
  }));
  
  
  
  
  
  
  Directive.registerAttribute("if", 500, Directive.$extend({
  
      $class: "Directive.attr.If",
      parentEl: null,
      prevEl: null,
      nextEl: null,
      el: null,
      initial: true,
      cfg: null,
      animate: false,
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self    = this;
  
          self.createCommentHolders(node, this.$class);
  
          //self.parentEl   = node.parentNode;
          self.cfg        = attr ? attr.config : {};
          self.animate    = !!self.cfg.animate;
  
          self.$super(scope, node, expr, renderer, attr);
      },
  
      onScopeDestroy: function() {
  
          var self    = this;
  
          self.prevEl = null;
          //self.parentEl = null;
          self.nextEl = null;
  
          self.$super();
      },
  
      onChange: function() {
          var self    = this,
              val     = self.watcher.getLastResult(),
              parent  = self.prevEl.parentNode,
              node    = self.node;
  
          var show    = function(){
              parent.insertBefore(node, self.nextEl);
          };
  
          var hide    = function() {
              parent.removeChild(node);
          };
  
          if (val) {
              self.initial || !self.animate ?
                  show() : animate(node, "enter", show);
          }
          else {
              if (node.parentNode) {
                  self.initial || !self.animate ?
                      hide() : animate(node, "leave").done(hide);
              }
          }
  
          self.$super(val);
  
          if (self.initial) {
              self.initial = false;
          }
          else {
              if (self.cfg.once) {
                  self.$destroy();
              }
          }
      }
  
  }));
  
  
  
  
  
  
  Directive.registerAttribute("in-focus", 500, Directive.$extend({
  
      $class: "Directive.attr.InFocus",
  
      onChange: function() {
  
          var self    = this;
  
          if (self.watcher.getLastResult()) {
              async(self.node.focus, self.node, [], 300);
          }
      }
  
  }));
  
  
  var toBool = function(val) {
      if (!val) { // real false, empty string, null, zero
          return false;
      }
      if (typeof val === "string") {
          val = val.toLowerCase();
          if (val === "false" || val === "no" || val === '0') {
              return false;
          }
      }
      return true;
  };
  
  
  
  
  Directive.registerAttribute("include", 1100,
      function(scope, node, tplExpr, parentRenderer, attr){
  
      var cfg = attr ? attr.config : {},
          asis = toBool(cfg.asis),
          html = cfg.html,
          tplCfg = {
              scope: scope,
              node: node,
              parentRenderer: parentRenderer,
              animate: !!cfg.animate,
              ownRenderer: !asis // do not render if asis=true
          };
  
      if (html) {
          tplCfg['html'] = html;
      }
      else {
          tplCfg['url'] = tplExpr;
      }
  
      var tpl = new Template(tplCfg);
  
      return false; // stop renderer
  });
  
  
  
  
  
  Directive.registerAttribute("init", 250, function(scope, node, expr){
      createFunc(expr)(scope);
  });
  
  
  
  (function(){
  
  var keys = {
      "enter": 13,
      "esc": 27,
      "backspace": 8
  };
  
  /*
  value is always an array in the end:
  [{keyCode: 1, handler: fn}, {...}]
  
  DO NOT MIX {key}="{...}" with  {key.enter}="{...}"
  
  NO:
  {key}="{...}"
  {key.enter}="{...}"
  
  YES:
  {key}="{...}"
  
  or
  
  {key}="[{...}]"
  {key.enter}="{...}"
  
   */
  
  Directive.registerAttribute("key", 1000, function(scope, node, expr, renderer, attr){
  
      var values = attr ? attr.values : null,
          parts, k, part, i, l;
  
      if (values) {
  
          parts = [];
  
          for (k in values) {
              part = values[k];
  
              if (keys[k]) {
                  k = keys[k];
              }
  
              if (part.substr(0,1) === '{') {
                  parts.push('{keyCode: ' + k + ', ' + part.substr(1));
              }
              else {
                  parts.push('{keyCode: ' + k + ', handler: "' + part + '"}');
              }
          }
          expr = '[' + parts.join(',') + ']';
      }
  
      var allCfg = createGetter(expr)(scope),
          uninstall = [];
  
      if (!isArray(allCfg)) {
          allCfg = [allCfg];
      }
  
      var createHandler = function(cfg) {
  
          var handler = cfg.handler;
          var context = cfg.context || scope;
          var h;
  
          delete cfg.handler;
          delete cfg.context;
  
          if (typeof handler === "string") {
              h = createFunc(handler);
              handler = function(){
                  return function(e) {
                      scope.$event = e;
                      h(scope);
                      scope.$event = null;
                      scope.$check();
                  };
              }(scope);
          }
  
          Input.get(node).onKey(cfg, handler, context);
  
          return function() {
              Input.get(node).unKey(cfg, handler, context);
          };
      };
  
      for (i = 0, l = allCfg.length; i < l; i++) {
          uninstall.push(createHandler(allCfg[i]));
      }
  
      return function() {
          var i, l;
          for (i = 0, l = uninstall.length; i < l; i++) {
              uninstall[i]();
          }
      };
  });
  
  }());
  
  
  
  
  
  
  
  
  Directive.registerAttribute("model", 1000, Directive.$extend({
  
      $class: "Directive.attr.Model",
      inProg: false,
      input: null,
      binding: null,
      updateRoot: false,
      changeCb: null,
      initial: false,
  
      autoOnChange: false,
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self    = this,
              cfg     = attr ? attr.config : {};
  
          self.node           = node;
          self.input          = Input.get(node, scope);
          self.updateRoot     = expr.indexOf('$root') + expr.indexOf('$parent') !== -2;
          self.binding        = cfg.binding || "both";
  
          if (cfg.change) {
              self.changeCb   = createFunc(cfg.change);
          }
  
          self.input.onChange(self.onInputChange, self);
  
          self.$super(scope, node, expr);
  
          var inputValue      = self.input.getValue(),
              scopeValue      = self.watcher.getLastResult();
  
          self.initial = true;
  
          if (scopeValue !== inputValue) {
              // scope value takes priority
              if (self.binding !== "input" && scopeValue !== undf) {
                  self.onChange(scopeValue);
              }
              else if (self.binding !== "scope" && inputValue !== undf) {
                  self.onInputChange(inputValue);
              }
          }
  
          self.initial = false;
      },
  
      onInputChange: function(val) {
  
          var self    = this,
              scope   = self.scope;
  
          if (self.binding !== "scope") {
  
              if (val && isString(val) && val.indexOf('\\{') !== -1) {
                  val = val.replace(/\\{/g, '{');
              }
  
              if (self.watcher.getLastResult() == val) {
                  return;
              }
  
              self.watcher.setValue(val);
  
              self.inProg = true;
              if (scope instanceof Scope) {
                  self.updateRoot ? scope.$root.$check() : scope.$check();
              }
              else {
                  self.watcher.checkAll();
              }
              self.inProg = false;
          }
      },
  
      destroy: function() {
  
          var self        = this;
          self.input.destroy();
          self.$super();
      },
  
  
      onChange: function() {
  
          var self    = this,
              val     = self.watcher.getLastResult(),
              binding = self.binding,
              ie;
  
          if (self.binding !== "input" && !self.inProg) {
  
              // when scope value changed but this field
              // is not in focus, it should try to
              // change input's value, but not react
              // to input's 'change' and 'input' events --
              // fields like select or radio may not have
              // this value in its options. that will change
              // value to undefined and bubble back to scope
              if (window.document.activeElement !== self.node) {
                  self.binding = "scope";
              }
  
              if ((ie = isIE()) && ie < 8) {
                  async(self.input.setValue, self.input, [val]);
              }
              else {
                  self.input.setValue(val);
              }
  
              self.binding = binding;
          }
  
          if (self.changeCb && !self.initial && val != self.watcher.getPrevValue()) {
              self.changeCb(self.scope);
          }
      }
  
  
  }));
  
  
  
  
  
  
  Directive.registerAttribute("options", 100, defineClass({
  
      $class: "Directive.attr.Options",
      $extends: Directive,
  
      model: null,
      store: null,
      getterFn: null,
      defOption: null,
      prevGroup: null,
      groupEl: null,
      fragment: null,
  
      $init: function(scope, node, expr) {
  
          var self    = this;
  
          self.parseExpr(expr);
  
          self.node       = node;
          self.scope      = scope;
          self.defOption  = node.options.length ? node.options[0] : null;
  
          while (node.firstChild) {
              node.removeChild(node.firstChild);
          }
  
          self.defOption && setAttr(self.defOption, "default-option", "");
  
          try {
              var value = createGetter(self.model)(scope);
              if (cs.isInstanceOf(value, "Store")) {
                  self.bindStore(value, "on");
              }
              else {
                  self.watcher = createWatchable(scope, self.model, self.onChange, self,
                      {filterLookup: filterLookup});
              }
          }
          catch (thrownError) {
              error(thrownError);
          }
  
          if (self.watcher) {
              self.renderAll();
          }
          else if (self.store) {
              self.renderStore();
          }
      },
  
      bindStore: function(store, mode) {
          var self = this;
          store[mode]("update", self.renderStore, self);
          self.store = store;
      },
  
      renderStore: function() {
          var self = this;
          self.render(self.store.current);
      },
  
      renderAll: function() {
          this.render(toArray(this.watcher.getValue()));
      },
  
      onChange: function() {
          this.renderAll();
      },
  
      renderOption: function(item, index, scope) {
  
          var self        = this,
              parent      = self.groupEl || self.fragment,
              msie        = isIE(),
              config,
              option;
  
          scope.item      = item;
          scope.$index    = index;
  
          if (self.defaultOptionTpl && isPlainObject(item)) {
              config      = item;
          }
          else {
              config      = self.getterFn(scope);
          }
  
          config.group    !== undf && (config.group = ""+config.group);
  
          if (config.group !== self.prevGroup) {
  
              if (config.group){
                  self.groupEl = parent = window.document.createElement("optgroup");
                  setAttr(parent, "label", config.group);
                  if (config.disabledGroup) {
                      setAttr(parent, "disabled", "disabled");
                  }
                  self.fragment.appendChild(parent);
              }
              else {
                  parent = self.fragment;
                  self.groupEl = null;
              }
          }
          self.prevGroup  = config.group;
  
          option  = window.document.createElement("option");
          setAttr(option, "value", config.value || "");
          option.text = config.name;
  
          if (msie && msie < 9) {
              option.innerHTML = config.name;
          }
          if (config.disabled) {
              setAttr(option, "disabled", "disabled");
          }
  
          parent.appendChild(option);
      },
  
      render: function(list) {
  
          var self        = this,
              node        = self.node,
              value       = getValue(node),
              def         = self.defOption,
              tmpScope    = self.scope.$new(),
              msie        = isIE(),
              parent, next,
              i, len;
  
          self.fragment   = window.document.createDocumentFragment();
          self.prevGroup  = null;
          self.groupEl    = null;
  
          while(node.firstChild) {
              node.removeChild(node.firstChild);
          }
  
          for (i = 0, len = list.length; i < len; i++) {
              self.renderOption(list[i], i, tmpScope);
          }
  
          if (def) {
              node.insertBefore(def, node.firstChild);
          }
  
          tmpScope.$destroy();
  
          // ie6 gives "unspecified error when trying to set option.selected"
          // on node.appendChild(fragment);
          // somehow this get fixed by detaching dom node
          // and attaching it back
          if (msie && msie < 8) {
              next = node.nextSibling;
              parent = node.parentNode;
              parent.removeChild(node);
          }
  
          node.appendChild(self.fragment);
          self.fragment = null;
  
          if (msie && msie < 8) {
              parent.insertBefore(node, next);
          }
  
          setValue(node, value);
      },
  
  
      parseExpr: function(expr) {
  
          var splitIndex  = expr.indexOf(" in "),
              model, item;
  
          if (splitIndex === -1) {
              model   = expr;
              item    = '{name: .item, value: .$index}';
              this.defaultOptionTpl = true;
          }
          else {
              model   = expr.substr(splitIndex + 4);
              item    = expr.substr(0, splitIndex);
              this.defaultOptionTpl = false;
          }
  
          this.model = model;
          this.getterFn = createGetter(item);
      },
  
      destroy: function() {
  
          var self = this;
  
          if (self.store){
              self.bindStore(self.store, "un");
          }
  
          self.$super();
  
      }
  
  }));
  
  
  
  
  
  
  
  (function(){
  
      var booleanAttrs = ["selected", "checked", "disabled", "readonly", "open", "required"],
          i, l;
  
      var PropertyDirective = defineClass({
  
          $extends: Directive,
  
          propName: null,
  
          $init: function(scope, node, expr, propName) {
              this.propName = propName;
              this.$super(scope, node, expr);
          },
  
          onChange: function(val) {
  
              var name = this.propName;
  
              val = !!val;
  
              if (val) {
                  setAttr(this.node, name, name);
              }
              else {
                  removeAttr(this.node, name);
              }
          }
      });
  
      for (i = 0, l = booleanAttrs.length; i < l; i++) {
          (function(name){
  
              Directive.registerAttribute("" + name, 1000, function(scope, node, expr){
                  return new PropertyDirective(scope, node, expr, name);
              });
  
          }(booleanAttrs[i]));
      }
  
  }());
  
  
  
  
  Directive.registerAttribute("ref", 200, function(scope, node, expr){
      scope[expr] = node;
  });
  
  
  
  
  Directive.registerAttribute("source-src", 1000, defineClass({
  
      $class: "Directive.attr.SourceSrc",
      $extends: Directive,
  
      usePreload: true,
      noCache: false,
      attr: null,
  
      lastPromise: null,
      src: null,
  
      $constructor: function(scope, node, expr, renderer, attr) {
  
          var self = this,
              cfg = attr ? attr.config : {};
  
          self.attr = attr;
  
          if (cfg.deferred) {
              self.$plugins.push("plugin.SrcDeferred");
          }
          /*if (cfg.preloadSize) {
              self.$plugins.push("plugin.SrcSize");
          }*/
          if (cfg.plugin) {
              var tmp = cfg.plugin.split(","),
                  i, l;
              for (i = 0, l = tmp.length; i < l; i++) {
                  self.$plugins.push(trim(tmp[i]));
              }
          }
  
          self.$super(scope, node, expr);
      },
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self = this,
              cfg = attr ? attr.config : {};
  
          if (cfg.noCache) {
              self.noCache = true;
          }
          self.$super(scope, node, expr);
      },
  
      onChange: function() {
          this.doChange();
      },
  
      doChange: function() {
          var self = this;
          
          if (self.$destroyed || self.$destroying) {
              return;
          }
  
          var src = self.watcher.getLastResult();
  
          if (!src) {
              return;
          }
  
          self.src = src;
  
          if (self.noCache) {
              src += (src.indexOf("?") !== -1 ? "&amp;" : "?") + "_" + (new Date).getTime();
          }
  
          if (self.node) {
              self.doChangeSource(src);
              self.onSrcChanged();
          }
      },
  
      doChangeSource: function(src) {
          var self = this,
              node = self.node,
              srcs = select("source", node),
              source = window.document.createElement("source"),
              i, l;
  
          if (srcs.length) {
              for (i  = 0, l = srcs.length; i < l; i++) {
                  node.removeChild(srcs[i]);
              }
          }
  
          setAttr(source, "src", src);
          node.appendChild(source);
      },
  
      onSrcChanged: function() {
  
      }
  }));
  
  
  
  
  var preloadImage = function() {
  
      var cache = {},
          loading = {},
          cacheCnt = 0;
  
  
      var preloadImage = function preloadImage(src) {
  
          if (cache[src] !== undefined) {
              if (cache[src] === false) {
                  return Promise.reject(src);
              }
              else {
                  return Promise.resolve(cache[src]);
              }
          }
  
          if (loading[src]) {
              return loading[src];
          }
  
          if (cacheCnt > 1000) {
              cache = {};
              cacheCnt = 0;
          }
  
          var doc = window.document,
              img = doc.createElement("img"),
              style = img.style,
              deferred = new Promise;
  
          loading[src] = deferred;
  
          deferred.always(function(){
              delete loading[src];
          });
  
          addListener(img, "load", function() {
              if (!cache[src]) {
                  cache[src] = {
                      src:    src,
                      width:  img ? img.width : null,
                      height: img ? img.height : null
                  };
                  cacheCnt++;
              }
              if (deferred) {
                  deferred.resolve(cache[src]);
              }
              if (img && img.parentNode) {
                  img.parentNode.removeChild(img);
              }
              img = null;
              style = null;
              deferred = null;
          });
  
          addListener(img, "error", function() {
              cache[src] = false;
              if (img && img.parentNode) {
                  img.parentNode.removeChild(img);
              }
              if (deferred) {
                  deferred.reject(src);
              }
          });
  
          deferred.abort = function() {
              if (img && img.parentNode) {
                  img.parentNode.removeChild(img);
              }
              if (deferred) {
                  deferred.reject(src);
              }
              img = null;
              style = null;
              deferred = null;
          };
  
          style.position = "absolute";
          style.visibility = "hidden";
          style.left = "-10000px";
          style.top = "0";
          doc.body.appendChild(img);
          img.src = src;
  
          return deferred;
      };
  
      preloadImage.check = function(src) {
          if (cache[src] !== undefined) {
              return cache[src];
          }
          return loading[src] || null;
      };
  
      return preloadImage;
  
  }();
  
  
  
  
  Directive.registerAttribute("src", 1000, defineClass({
  
      $class: "Directive.attr.Src",
      $extends: Directive,
  
      queue: null,
      usePreload: true,
      noCache: false,
      attr: null,
  
      lastPromise: null,
      src: null,
  
      $constructor: function(scope, node, expr, renderer, attr) {
  
          var self = this,
              cfg = attr ? attr.config : {};
  
          self.attr = attr;
  
          if (cfg.deferred) {
              self.$plugins.push("plugin.SrcDeferred");
          }
          if (cfg.preloadSize) {
              self.$plugins.push("plugin.SrcSize");
          }
          if (cfg.plugin) {
              var tmp = cfg.plugin.split(","),
                  i, l;
              for (i = 0, l = tmp.length; i < l; i++) {
                  self.$plugins.push(trim(tmp[i]));
              }
          }
  
          self.$super(scope, node, expr);
      },
  
      $init: function(scope, node, expr, renderer, attr) {
  
          var self = this,
              cfg = attr ? attr.config : {};
  
          if (cfg.noCache) {
              self.noCache = true;
          }
  
          if (cfg.noPreload) {
              self.usePreload = false;
          }
          else {
              node.style.visibility = "hidden"
          }
  
          self.queue = new Queue({auto: true, async: true, mode: Queue.REPLACE, thenable: true});
          self.$super(scope, node, expr);
      },
  
  
      onChange: function() {
          var self = this;
          self.cancelPrevious();
          if (self.usePreload) {
              self.node.style.visibility = "hidden";
          }
          self.queue.add(self.doChange, self);
      },
  
      doChange: function() {
  
          var self = this;
  
          if (self.$destroyed || self.$destroying) {
              return;
          }
  
          var src = self.watcher.getLastResult();
  
          if (!src) {
              return;
          }
  
          self.src = src;
  
          if (self.noCache) {
              src += (src.indexOf("?") !== -1 ? "&amp;" : "?") + "_" + (new Date).getTime();
          }
  
          if (self.usePreload) {
              self.lastPromise = preloadImage(src);
              if (self.lastPromise) {
                  self.lastPromise.done(self.onImagePreloaded, self);
              }
          }
          else {
              if (self.node) {
                  self.node.src = src;
                  setAttr(self.node, "src", src);
                  self.onSrcChanged();
              }
          }
      },
  
      cancelPrevious: function() {
          var self = this;
  
          if (self.lastPromise) {
              if (self.lastPromise.isPending()) {
                  self.lastPromise.abort();
              }
              self.lastPromise = null;
          }
      },
  
      onImagePreloaded: function() {
          var self = this,
              src = self.src;
  
          if (self && self.node) {
              raf(function(){
                  if (self.node) {
                      self.node.src = src;
                      setAttr(self.node, "src", src);
                      self.onSrcChanged();
                      self.node.style.visibility = "";
                      self.scope.$scheduleCheck(50);
                  }
              });
          }
          self.lastPromise = null;
      },
  
      onSrcChanged: function() {
  
      },
  
      onScopeReset: function() {
          this.cancelPrevious();
          this.$super();
      },
  
      destroy: function() {
  
          var self = this;
  
          if (!self.$destroyed) {
              self.cancelPrevious();
              self.queue.destroy();
              self.$super();
          }
      }
  }));
  
  var removeStyle = (function() {
  
      var div = window.document.createElement("div");
  
      if (div.style.removeProperty) {
          return function(node, name) {
              node.style.removeProperty(name);
          };
      }
      else {
          return function(node, name) {
              node.style.removeAttribute(name);
          };
      }
  
  }());
  
  
  
  /*
  value is always an object in the end
  DO NOT MIX style="{}" with style.prop="expression".
   */
  
  
  Directive.registerAttribute("style", 1000, Directive.$extend({
  
      $class: "Directive.attr.Style",
      $init: function(scope, node, expr, renderer, attr) {
  
          var values = attr ? attr.values : null,
              parts, k;
  
          if (values) {
              parts = [];
              for (k in values) {
                  parts.push("'" + k + "'" + ': ' + values[k]);
              }
              expr = '{' + parts.join(', ') + '}';
          }
  
          this.$super(scope, node, expr);
      },
  
      onChange: function() {
  
          var self    = this,
              node    = self.node,
              style   = node.style,
              props   = self.watcher.getLastResult(),
              prev    = self.watcher.getPrevValue(),
              k, trg;
  
          for (k in prev) {
              if (!props || props[k] === undf) {
                  removeStyle(node, k);
              }
          }
  
          if (props) {
              for (k in props) {
  
                  trg = toCamelCase(k);
  
                  if (props[k] !== undf && props[k] !== null) {
                      style[trg] = props[k];
                  }
                  else {
                      removeStyle(node, k);
                  }
              }
          }
      }
  }));
  
  
  
  function parentData(node, key) {
  
      var val;
  
      while (node) {
          val = data(node ,key);
          if (val !== undf) {
              return val;
          }
          node  = node.parentNode;
      }
  
      return undf;
  };
  
  
  
  function transclude(node, replace) {
  
      var contents  = parentData(node, 'mjs-transclude');
  
      if (contents) {
  
          if (node.firstChild) {
              data(node, "mjs-transclude", toFragment(node.childNodes));
          }
  
          var parent      = node.parentNode,
              next        = node.nextSibling,
              cloned      = clone(contents),
              children    = toArray(cloned.childNodes);
  
          if (replace) {
              parent.removeChild(node);
              parent.insertBefore(cloned, next);
          }
          else {
              node.appendChild(cloned);
          }
  
          return children;
      }
  
      return null;
  };
  
  
  
  Directive.registerAttribute("transclude", 1000, function(scope, node) {
      return transclude(node);
  });
  
  
  
      /*
          Update scope on given event.
          Not exactly template's business, but still
      */
  Directive.registerAttribute("update-on", 1000,
      function(scope, node, expr, renderer, attr){
  
      var values = attr ? attr.values : null,
          cfg = attr ? attr.config : {},
          parts, k, part,
          execFn;
  
      if (values) {
  
          parts = [];
  
          for (k in values) {
              part = values[k];
              parts.push("['" + k + "', " + part + ']');
          }
          expr = '[' + parts.join(',') + ']';
      }
  
      var cfgs = createGetter(expr)(scope);
  
      if (cfg.code) {
          var code = createFunc(cfg.code);
          execFn = function() {
              scope.$event = toArray(arguments);
              code(scope);
              scope.$event = null;
              scope.$check();
          };
      }
      else {
          execFn = scope.$check;
      }
  
      var toggle = function(mode) {
  
          var cfg, event, obj, i, l, fn;
  
          for (i = 0, l = cfgs.length; i < l; i++) {
              cfg = cfgs[i];
              event = cfg[0];
              obj = cfg[1];
  
              if (obj.$destroyed || obj.$destroying) {
                  continue;
              }
  
              if (obj && event && (fn = (obj[mode] || obj['$' + mode]))) {
                  fn.call(obj, event, execFn, scope);
              }
          }
      };
  
      toggle("on");
  
      return function() {
          if (toggle) {
              toggle("un");
              cfgs = null;
              toggle = null;
          }
      };
  });
  
  
  
  Directive.registerAttribute("view", 200, function(scope, node, cls, parentRenderer, attr) {
      var cfg = {scope: scope, node: node};
      resolveComponent(
          cls || "MetaphorJs.View",
          cfg,
          scope, node,
          [cfg, attr]
      );
      return false;
  });
  
  
  
  
  Directive.registerTag("transclude", function(scope, node) {
      return transclude(node, true);
  });
  
  
  
  /**
   * @param {Function} fn
   * @param {Window} w optional window object
   */
  function onReady(fn, w) {
  
      var done    = false,
          top     = true,
          win     = w || window,
          root, doc,
  
          init    = function(e) {
              if (e.type == 'readystatechange' && doc.readyState != 'complete') {
                  return;
              }
  
              removeListener(e.type == 'load' ? win : doc, e.type, init);
  
              if (!done && (done = true)) {
                  fn.call(win, e.type || e);
              }
          },
  
          poll = function() {
              try {
                  root.doScroll('left');
              } catch(thrownError) {
                  setTimeout(poll, 50);
                  return;
              }
  
              init('poll');
          };
  
      doc     = win.document;
      root    = doc.documentElement;
  
      if (doc.readyState == 'complete') {
          fn.call(win, 'lazy');
      }
      else {
          if (doc.createEventObject && root.doScroll) {
              try {
                  top = !win.frameElement;
              } catch(thrownError) {}
  
              top && poll();
          }
          addListener(doc, 'DOMContentLoaded', init);
          addListener(doc, 'readystatechange', init);
          addListener(win, 'load', init);
      }
  };
  
  
  
  function initApp(node, cls, data, autorun) {
  
  
      var attrs = getAttrSet(node, function(name) {
          return !!nsGet("directive.attr." + name, true);
      });
  
      var cfg = attrs.directive.app ? attrs.directive.app.config : {},
          i, l;
  
      if (attrs.subnames['app']) {
          for (i = 0, l = attrs.subnames['app'].length; i < l; i++) {
              removeAttr(node, attrs.subnames[i]);
          }
      }
  
      try {
          var p = resolveComponent(cls || "MetaphorJs.App", extend({}, cfg), data, node, [node, data]);
  
          if (autorun !== false) {
              return p.done(function(app){
                  app.run();
              });
          }
          else {
              return p;
          }
      }
      catch (thrownError) {
          error(thrownError);
          return Promise.reject(thrownError);
      }
  };
  
  
  
  function run(w, appData) {
  
      var win = w || window;
  
      if (!win) {
          throw "Window object neither defined nor provided";
      }
  
      onReady(function() {
  
          var appNodes    = select("[mjs-app]", win.document),
              i, l, el;
  
          for (i = -1, l = appNodes.length; ++i < l;){
              el      = appNodes[i];
              initApp(
                  el,
                  getAttr(el, "mjs-app"),
                  appData,
                  true
              );
          }
      }, win);
  
  };
  
  
  
  
  run();
  
  
  }());/* BUNDLE END 003 */