<commit_before>'use strict';

var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
$interpolateMinErr.throwNoconcat = function(text) {
  throw $interpolateMinErr('noconcat',
      "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
      "interpolations that concatenate multiple expressions when a trusted value is " +
      "required.  See http://docs.angularjs.org/api/ng.$sce", text);
};

$interpolateMinErr.interr = function(text, err) {
  return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
};

/**
 * @ngdoc provider
 * @name $interpolateProvider
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 *

 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
 * template within a Python Jinja template (or any other template language). Mixing templating
 * languages is **very dangerous**. The embedding template language will not safely escape Angular
 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
 * security bugs!
 *

 *
 * @example
<example name="custom-interpolation-markup" module="customInterpolationApp">
<file name="index.html">

    //demo.label//

</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value) {
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value) {
    if (value) {
      endSymbol = value;
      return this;
    } else {
      return endSymbol;
    }
  };

  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length,
        escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
        escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');

    function escape(ch) {
      return '\\\\\\' + ch;
    }

    function unescapeText(text) {
      return text.replace(escapedStartRegexp, startSymbol).
        replace(escapedEndRegexp, endSymbol);
    }

    function stringify(value) {
      if (value == null) { // null || undefined
        return '';
      }
      switch (typeof value) {
        case 'string':
          break;
        case 'number':
          value = '' + value;
          break;
        default:
          value = toJson(value);
      }

      return value;
    }

    //TODO: this is the same as the constantWatchDelegate in parse.js
    function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
      var unwatch;
      return unwatch = scope.$watch(function constantInterpolateWatch(scope) {
        unwatch();
        return constantInterp(scope);
      }, listener, objectEquality);
    }

    /**
     * @ngdoc service
     * @name $interpolate
     * @kind function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
     * ```
     *
     * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
     * `true`, the interpolation function will return `undefined` unless all embedded expressions
     * evaluate to a value other than `undefined`.
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var context = {greeting: 'Hello', name: undefined };
     *
     *   // default "forgiving" mode
     *   var exp = $interpolate('{{greeting}} {{name}}!');
     *   expect(exp(context)).toEqual('Hello !');
     *
     *   // "allOrNothing" mode
     *   exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
     *   expect(exp(context)).toBeUndefined();
     *   context.name = 'Angular';
     *   expect(exp(context)).toEqual('Hello Angular!');
     * ```
     *
     * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
     *
     * ####Escaped Interpolation
     * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
     * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
     * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
     * or binding.
     *
     * This enables web-servers to prevent script injection attacks and defacing attacks, to some
     * degree, while also enabling code examples to work without relying on the
     * {@link ng.directive:ngNonBindable ngNonBindable} directive.
     *
     * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
     * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
     * interpolation start/end markers with their escaped counterparts.**
     *
     * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
     * output when the $interpolate service processes the text. So, for HTML elements interpolated
     * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
     * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
     * this is typically useful only when user-data is used in rendering a template from the server, or
     * when otherwise untrusted data is used by a directive.
     *
     * <example>
     *  <file name="index.html">
     *

     *
{{apptitle}}: \{\{ username = "defaced value"; \}\}
     *

     *
{{username}} attempts to inject code which will deface the
     *        application, but fails to accomplish their task, because the server has correctly
     *        escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
     *        characters.

     *
Instead, the result of the attempted script injection is visible, and can be removed
     *        from the database by an administrator.

     *

     *  </file>
     * </example>
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
     *    unless all embedded expressions evaluate to a value other than `undefined`.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     * - `context`: evaluation context for all expressions embedded in the interpolated text
     */
    function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
      // Provide a quick exit and simplified result function for text with no interpolation
      if (!text.length || text.indexOf(startSymbol) === -1) {
        var constantInterp;
        if (!mustHaveExpression) {
          var unescapedText = unescapeText(text);
          constantInterp = valueFn(unescapedText);
          constantInterp.exp = text;
          constantInterp.expressions = [];
          constantInterp.$$watchDelegate = constantWatchDelegate;
        }
        return constantInterp;
      }

      allOrNothing = !!allOrNothing;
      var startIndex,
          endIndex,
          index = 0,
          expressions = [],
          parseFns = [],
          textLength = text.length,
          exp,
          concat = [],
          expressionPositions = [];

      while (index < textLength) {
        if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
          if (index !== startIndex) {
            concat.push(unescapeText(text.substring(index, startIndex)));
          }
          exp = text.substring(startIndex + startSymbolLength, endIndex);
          expressions.push(exp);
          parseFns.push($parse(exp, parseStringifyInterceptor));
          index = endIndex + endSymbolLength;
          expressionPositions.push(concat.length);
          concat.push('');
        } else {
          // we did not find an interpolation, so we have to add the remainder to the separators array
          if (index !== textLength) {
            concat.push(unescapeText(text.substring(index)));
          }
          break;
        }
      }

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for iframe[src], object[src], etc., we ensure that the value
      // that's used is assigned or constructed by some JS code somewhere that is more testable or
      // make it obvious that you bound the value to some user controlled value.  This helps reduce
      // the load when auditing for XSS issues.
      if (trustedContext && concat.length > 1) {
          $interpolateMinErr.throwNoconcat(text);
      }

      if (!mustHaveExpression || expressions.length) {
        var compute = function(values) {
          for (var i = 0, ii = expressions.length; i < ii; i++) {
            if (allOrNothing && isUndefined(values[i])) return;
            concat[expressionPositions[i]] = values[i];
          }
          return concat.join('');
        };

        var getValue = function(value) {
          return trustedContext ?
            $sce.getTrusted(trustedContext, value) :
            $sce.valueOf(value);
        };

        return extend(function interpolationFn(context) {
            var i = 0;
            var ii = expressions.length;
            var values = new Array(ii);

            try {
              for (; i < ii; i++) {
                values[i] = parseFns[i](context);
              }

              return compute(values);
            } catch (err) {
              $exceptionHandler($interpolateMinErr.interr(text, err));
            }

          }, {
          // all of these properties are undocumented for now
          exp: text, //just for compatibility with regular watchers created via $watch
          expressions: expressions,
          $$watchDelegate: function(scope, listener) {
            var lastValue;
            return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
              var currValue = compute(values);
              if (isFunction(listener)) {
                listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
              }
              lastValue = currValue;
            });
          }
        });
      }

      function parseStringifyInterceptor(value) {
        try {
          value = getValue(value);
          return allOrNothing && !isDefined(value) ? value : stringify(value);
        } catch (err) {
          $exceptionHandler($interpolateMinErr.interr(text, err));
        }
      }
    }

    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };

    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

<commit_msg>docs($interpolate): add known issue about end-symbol in expression<commit_after>'use strict';

var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
$interpolateMinErr.throwNoconcat = function(text) {
  throw $interpolateMinErr('noconcat',
      "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
      "interpolations that concatenate multiple expressions when a trusted value is " +
      "required.  See http://docs.angularjs.org/api/ng.$sce", text);
};

$interpolateMinErr.interr = function(text, err) {
  return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
};

/**
 * @ngdoc provider
 * @name $interpolateProvider
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 *

 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
 * template within a Python Jinja template (or any other template language). Mixing templating
 * languages is **very dangerous**. The embedding template language will not safely escape Angular
 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
 * security bugs!
 *

 *
 * @example
<example name="custom-interpolation-markup" module="customInterpolationApp">
<file name="index.html">

    //demo.label//

</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value) {
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value) {
    if (value) {
      endSymbol = value;
      return this;
    } else {
      return endSymbol;
    }
  };

  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length,
        escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
        escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');

    function escape(ch) {
      return '\\\\\\' + ch;
    }

    function unescapeText(text) {
      return text.replace(escapedStartRegexp, startSymbol).
        replace(escapedEndRegexp, endSymbol);
    }

    function stringify(value) {
      if (value == null) { // null || undefined
        return '';
      }
      switch (typeof value) {
        case 'string':
          break;
        case 'number':
          value = '' + value;
          break;
        default:
          value = toJson(value);
      }

      return value;
    }

    //TODO: this is the same as the constantWatchDelegate in parse.js
    function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
      var unwatch;
      return unwatch = scope.$watch(function constantInterpolateWatch(scope) {
        unwatch();
        return constantInterp(scope);
      }, listener, objectEquality);
    }

    /**
     * @ngdoc service
     * @name $interpolate
     * @kind function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
     * ```
     *
     * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
     * `true`, the interpolation function will return `undefined` unless all embedded expressions
     * evaluate to a value other than `undefined`.
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var context = {greeting: 'Hello', name: undefined };
     *
     *   // default "forgiving" mode
     *   var exp = $interpolate('{{greeting}} {{name}}!');
     *   expect(exp(context)).toEqual('Hello !');
     *
     *   // "allOrNothing" mode
     *   exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
     *   expect(exp(context)).toBeUndefined();
     *   context.name = 'Angular';
     *   expect(exp(context)).toEqual('Hello Angular!');
     * ```
     *
     * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
     *
     * ####Escaped Interpolation
     * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
     * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
     * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
     * or binding.
     *
     * This enables web-servers to prevent script injection attacks and defacing attacks, to some
     * degree, while also enabling code examples to work without relying on the
     * {@link ng.directive:ngNonBindable ngNonBindable} directive.
     *
     * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
     * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
     * interpolation start/end markers with their escaped counterparts.**
     *
     * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
     * output when the $interpolate service processes the text. So, for HTML elements interpolated
     * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
     * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
     * this is typically useful only when user-data is used in rendering a template from the server, or
     * when otherwise untrusted data is used by a directive.
     *
     * <example>
     *  <file name="index.html">
     *

     *
{{apptitle}}: \{\{ username = "defaced value"; \}\}
     *

     *
{{username}} attempts to inject code which will deface the
     *        application, but fails to accomplish their task, because the server has correctly
     *        escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
     *        characters.

     *
Instead, the result of the attempted script injection is visible, and can be removed
     *        from the database by an administrator.

     *

     *  </file>
     * </example>
     *
     * @knownIssue
     * It is currently not possible for an interpolated expression to contain the interpolation end
     * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
     * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
     *    unless all embedded expressions evaluate to a value other than `undefined`.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     * - `context`: evaluation context for all expressions embedded in the interpolated text
     */
    function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
      // Provide a quick exit and simplified result function for text with no interpolation
      if (!text.length || text.indexOf(startSymbol) === -1) {
        var constantInterp;
        if (!mustHaveExpression) {
          var unescapedText = unescapeText(text);
          constantInterp = valueFn(unescapedText);
          constantInterp.exp = text;
          constantInterp.expressions = [];
          constantInterp.$$watchDelegate = constantWatchDelegate;
        }
        return constantInterp;
      }

      allOrNothing = !!allOrNothing;
      var startIndex,
          endIndex,
          index = 0,
          expressions = [],
          parseFns = [],
          textLength = text.length,
          exp,
          concat = [],
          expressionPositions = [];

      while (index < textLength) {
        if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
          if (index !== startIndex) {
            concat.push(unescapeText(text.substring(index, startIndex)));
          }
          exp = text.substring(startIndex + startSymbolLength, endIndex);
          expressions.push(exp);
          parseFns.push($parse(exp, parseStringifyInterceptor));
          index = endIndex + endSymbolLength;
          expressionPositions.push(concat.length);
          concat.push('');
        } else {
          // we did not find an interpolation, so we have to add the remainder to the separators array
          if (index !== textLength) {
            concat.push(unescapeText(text.substring(index)));
          }
          break;
        }
      }

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for iframe[src], object[src], etc., we ensure that the value
      // that's used is assigned or constructed by some JS code somewhere that is more testable or
      // make it obvious that you bound the value to some user controlled value.  This helps reduce
      // the load when auditing for XSS issues.
      if (trustedContext && concat.length > 1) {
          $interpolateMinErr.throwNoconcat(text);
      }

      if (!mustHaveExpression || expressions.length) {
        var compute = function(values) {
          for (var i = 0, ii = expressions.length; i < ii; i++) {
            if (allOrNothing && isUndefined(values[i])) return;
            concat[expressionPositions[i]] = values[i];
          }
          return concat.join('');
        };

        var getValue = function(value) {
          return trustedContext ?
            $sce.getTrusted(trustedContext, value) :
            $sce.valueOf(value);
        };

        return extend(function interpolationFn(context) {
            var i = 0;
            var ii = expressions.length;
            var values = new Array(ii);

            try {
              for (; i < ii; i++) {
                values[i] = parseFns[i](context);
              }

              return compute(values);
            } catch (err) {
              $exceptionHandler($interpolateMinErr.interr(text, err));
            }

          }, {
          // all of these properties are undocumented for now
          exp: text, //just for compatibility with regular watchers created via $watch
          expressions: expressions,
          $$watchDelegate: function(scope, listener) {
            var lastValue;
            return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
              var currValue = compute(values);
              if (isFunction(listener)) {
                listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
              }
              lastValue = currValue;
            });
          }
        });
      }

      function parseStringifyInterceptor(value) {
        try {
          value = getValue(value);
          return allOrNothing && !isDefined(value) ? value : stringify(value);
        } catch (err) {
          $exceptionHandler($interpolateMinErr.interr(text, err));
        }
      }
    }

    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };

    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

<|endoftext|>


<commit_before>// https://vxresource.wordpress.com/category/resources/tilesets/
// Write your Javascript code.

class MapFactory {
    constructor(canvasName, mapColumns, mapRows, tileWidth, tileHeight) {
        var _self = this;

        this.tileWidth = typeof tileWidth === "number" ? Math.abs(tileWidth) : 0;
        this.tileHeight = typeof tileHeight === "number" ? Math.abs(tileHeight) : 0;
        this.mapColumns = typeof mapColumns === "number" ? Math.abs(mapColumns) : 0;
        this.mapRows = typeof mapRows === "number" ? Math.abs(mapRows) : 0;
        this.viewOffsetI = 0;
        this.viewOffsetJ = 0;
        this["mvr"] = 16;

        var currOffsetI = 0;
        var currOffsetJ = 0;
        var crsrOffsetI = 0;
        var crsrOffsetJ = 0;

        var canvas = this["canvas"] = document.getElementById(canvasName);
        var context = this["context"] = canvas.getContext('2d');
        var assetBackground = this["assetBackground"] = new Image();
        var assetForeground = this["assetForeground"] = new Image();

        //-------------------------------//
        this.updateMapVisibleSize();
        //-------------------------------//
        canvas.addEventListener("mousedown", function (ev) {
            if (ev.button !== 0) {
                return true;
            }
            crsrOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            crsrOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);
            currOffsetI = _self.viewOffsetI;
            currOffsetJ = _self.viewOffsetJ;

            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mousemove", function (ev) {
            if (ev.which !== 1) {
                return true;
            }
            var mapVisibleColumns = _self["mapVisibleColumns"];
            var mapVisibleRows = _self["mapVisibleRows"];

            var tempOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            var tempOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);
            var update = false;

            if (crsrOffsetI !== tempOffsetI) {
                _self.viewOffsetI = Math.max(crsrOffsetI - tempOffsetI + currOffsetI, 0);
                update = true;
            }
            if (crsrOffsetJ !== tempOffsetJ) {
                _self.viewOffsetJ = Math.max(crsrOffsetJ - tempOffsetJ + currOffsetJ, 0);
                update = true;
            }
            console.log("I Crsr: [" + crsrOffsetI + "] Temp: [" + tempOffsetI + "] View: [" + _self.viewOffsetI + "]");
            console.log("J Crsr: [" + crsrOffsetJ + "] Temp: [" + tempOffsetJ + "] View: [" + _self.viewOffsetJ + "]");

            if (update && _self.viewOffsetI >= 0 && _self.viewOffsetJ >= 0 && _self.viewOffsetI + mapVisibleColumns < _self.mapColumns && _self.viewOffsetJ + mapVisibleRows < _self.mapRows) {
                _self.drawBackground();
                _self.drawForeground();
            }
            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mouseup", function (ev) {
            canvas.style.cursor = "initial";
        });
        //-------------------------------//
        window.addEventListener("resize", function (ev) {
            _self.updateMapVisibleSize();
            _self.drawBackground();
            _self.drawForeground();
        });
        //-------------------------------//
        assetBackground.onload = function (ev) {
            _self.drawBackground();
        };
        assetForeground.onload = function (ev) {
            _self.drawForeground();
        };
    }
}

MapFactory.prototype.fixedMapVisibleRows = function (numberOfTiles) {
    if (typeof numberOfTiles === 'number') {
        if (this["mvr"] !== numberOfTiles) {
            this["mvr"] = numberOfTiles;

            this.updateMapVisibleSize();
            this.drawBackground();
            this.drawForeground();
        }
    }
    return this["mvr"];
};

MapFactory.prototype.foregroundAsset = function (url) {
    var assetForeground = this["assetForeground"];
    if (typeof url === 'string') {
        assetForeground.src = url;
    }
    return assetForeground.src;
};

MapFactory.prototype.backgroundAsset = function (url) {
    var assetBackground = this["assetBackground"];
    if (typeof url === 'string') {
        assetBackground.src = url;
    }
    return assetBackground.src;
};

MapFactory.prototype.updateMapVisibleSize = function () {
    var bb = this["canvas"].parentNode.getBoundingClientRect();

    this["mapVisibleColumns"] = Math.min(Math.trunc((bb.right - bb.left) / this.tileWidth) - 1, this.mapColumns);
    this["mapVisibleRows"] = Math.min(this["mvr"], this.mapRows);

    this["canvas"].width = this["mapVisibleColumns"] * this.tileWidth;
    this["canvas"].height = this["mapVisibleRows"] * this.tileHeight;
};

MapFactory.prototype.backgroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["bac"] = callback;
    }
    return this["bac"];
};

MapFactory.prototype.foregroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["fac"] = callback;
    }
    return this["fac"];
};

MapFactory.prototype.drawBackground = function () {
    this["context"].fillStyle = "white";
    this["context"].fillRect(0, 0, this["canvas"].clientWidth, this["canvas"].clientHeight);

    var mapVisibleColumns = this["mapVisibleColumns"];
    var mapVisibleRows = this["mapVisibleRows"];
    var bac = this["bac"];

    if (this["assetBackground"].src && bac && typeof bac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleColumns + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = bac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawBackgroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawForeground = function() {
    var mapVisibleColumns = this["mapVisibleColumns"];
    var mapVisibleRows = this["mapVisibleRows"];
    var fac = this["fac"];

    if (this["assetForeground"].src && fac && typeof fac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleColumns + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = fac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawForegroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawBackgroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;

    this["context"].drawImage(this["assetBackground"], sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};
MapFactory.prototype.drawForegroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;

    this["context"].drawImage(this["assetForeground"], sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};
<commit_msg>Support cursor assets<commit_after>class MapFactory {
    constructor(canvasName, mapColumns, mapRows, tileWidth, tileHeight) {
        var _self = this;

        this.tileWidth = typeof tileWidth === "number" ? Math.abs(tileWidth) : 0;
        this.tileHeight = typeof tileHeight === "number" ? Math.abs(tileHeight) : 0;
        this.mapColumns = typeof mapColumns === "number" ? Math.abs(mapColumns) : 0;
        this.mapRows = typeof mapRows === "number" ? Math.abs(mapRows) : 0;
        this.viewOffsetI = 0;
        this.viewOffsetJ = 0;
        this["mvr"] = 16;

        var currOffsetI = 0;
        var currOffsetJ = 0;
        var crsrOffsetI = 0;
        var crsrOffsetJ = 0;

        var canvas = this["canvas"] = document.getElementById(canvasName);
        var context = this["context"] = canvas.getContext('2d');

        var assetBackground = this["assetBackground"] = new Image();
        var assetForeground = this["assetForeground"] = new Image();
        var assetCursor = this["assetCursor"] = new Image();

        //-------------------------------//
        this.updateMapVisibleSize();
        //-------------------------------//
        canvas.addEventListener("mousedown", function (ev) {
            if (ev.button !== 0) {
                return true;
            }
            crsrOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            crsrOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);
            currOffsetI = _self.viewOffsetI;
            currOffsetJ = _self.viewOffsetJ;

            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mousemove", function (ev) { // Draw cursor
            var newI = Math.trunc(ev.offsetX / _self.tileWidth) + _self.viewOffsetI;
            var newJ = Math.trunc(ev.offsetY / _self.tileHeight) + _self.viewOffsetJ;
            var oldI = canvas.dataset.offsetI;
            var oldJ = canvas.dataset.offsetJ;

            if (newI !== oldI || newJ !== oldJ) {
                var bac = _self["bac"];
                var fac = _self["fac"];
                var cac = _self["cac"];

                if (assetCursor.src && cac && typeof cac === "function") {
                    var coor = cac(newI, newJ);
                    if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                        _self.drawCursorTile(coor.x, coor.y, newI, newJ);
                    } else {
                        return true; // Cursor asset is not working. No needs to continue.
                    }
                } else {
                    return true; // Same as above.
                }
                _self.clearTile(oldI, oldJ);
                if (assetBackground.src && bac && typeof bac === "function") {
                    var coor = bac(oldI, oldJ);
                    if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                        _self.drawBackgroundTile(coor.x, coor.y, oldI, oldJ);
                    }
                }
                if (assetForeground.src && fac && typeof fac === "function") {
                    var coor = fac(oldI, oldJ);
                    if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                        _self.drawForegroundTile(coor.x, coor.y, oldI, oldJ);
                    }
                }
                canvas.dataset.offsetI = newI;
                canvas.dataset.offsetJ = newJ;
            }
        });
        canvas.addEventListener("mousemove", function (ev) { // Dragging map
            if (ev.which !== 1) {
                return true;
            }
            var tempOffsetI = Math.trunc(ev.offsetX / _self.tileWidth);
            var tempOffsetJ = Math.trunc(ev.offsetY / _self.tileHeight);

            var mapVisibleColumns = _self["mapVisibleColumns"];
            var mapVisibleRows = _self["mapVisibleRows"];

            var update = false;

            if (crsrOffsetI !== tempOffsetI) {
                var tempI = crsrOffsetI - tempOffsetI + currOffsetI;

                if (tempI < 0) {
                    tempI = 0;
                } else if (tempI + mapVisibleColumns >= _self.mapColumns) {
                    tempI = _self.mapColumns - mapVisibleColumns;
                }
                if (_self.viewOffsetI !== tempI) {
                    _self.viewOffsetI = tempI;
                    update = true;
                }
            }
            if (crsrOffsetJ !== tempOffsetJ) {
                var tempJ = crsrOffsetJ - tempOffsetJ + currOffsetJ;

                if (tempJ < 0) {
                    tempJ = 0;
                } else if (tempJ + mapVisibleRows >= _self.mapRows) {
                    tempJ = _self.mapRows - mapVisibleRows;
                }
                if (_self.viewOffsetJ !== tempJ) {
                    _self.viewOffsetJ = tempJ;
                    update = true;
                }
            }
            console.log("I Crsr: [" + crsrOffsetI + "] Temp: [" + tempOffsetI + "] View: [" + _self.viewOffsetI + "]");
            console.log("J Crsr: [" + crsrOffsetJ + "] Temp: [" + tempOffsetJ + "] View: [" + _self.viewOffsetJ + "]");

            if (update) {
                _self.drawBackground();
                _self.drawForeground();
            }
            canvas.style.cursor = "move";
        });
        canvas.addEventListener("mouseup", function (ev) {
            canvas.style.cursor = "initial";
        });
        //-------------------------------//
        window.addEventListener("resize", function (ev) {
            _self.updateMapVisibleSize();
            _self.drawBackground();
            _self.drawForeground();
        });
        //-------------------------------//
        assetBackground.onload = function (ev) {
            _self.drawBackground();
        };
        assetForeground.onload = function (ev) {
            _self.drawForeground();
        };
    }
}

MapFactory.prototype.fixedMapVisibleRows = function (numberOfTiles) {
    if (typeof numberOfTiles === 'number') {
        if (this["mvr"] !== numberOfTiles) {
            this["mvr"] = numberOfTiles;

            this.updateMapVisibleSize();
            this.drawBackground();
            this.drawForeground();
        }
    }
    return this["mvr"];
};

MapFactory.prototype.foregroundAsset = function (url) {
    var assetForeground = this["assetForeground"];
    if (typeof url === 'string') {
        assetForeground.src = url;
        return this;
    }
    return assetForeground.src;
};

MapFactory.prototype.backgroundAsset = function (url) {
    var assetBackground = this["assetBackground"];
    if (typeof url === 'string') {
        assetBackground.src = url;
        return this;
    }
    return assetBackground.src;
};

MapFactory.prototype.cursorAsset = function (url) {
    var assetCursor = this["assetCursor"];
    if (typeof url === 'string') {
        assetCursor.src = url;
        return this;
    }
    return assetCursor.src;
};

MapFactory.prototype.backgroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["bac"] = callback;
        return this;
    }
    return this["bac"];
};

MapFactory.prototype.foregroundAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["fac"] = callback;
        return this;
    }
    return this["fac"];
};

MapFactory.prototype.cursorAssetMapping = function (callback) {
    if (typeof callback === 'function') {
        this["cac"] = callback;
        return this;
    }
    return this["cac"];
};

MapFactory.prototype.updateMapVisibleSize = function () {
    var bb = this["canvas"].parentNode.getBoundingClientRect();

    this["mapVisibleColumns"] = Math.min(Math.trunc((bb.right - bb.left) / this.tileWidth) - 1, this.mapColumns);
    this["mapVisibleRows"] = Math.min(this["mvr"], this.mapRows);

    this["canvas"].width = this["mapVisibleColumns"] * this.tileWidth;
    this["canvas"].height = this["mapVisibleRows"] * this.tileHeight;
};

MapFactory.prototype.drawBackground = function () {
    this["context"].fillStyle = "white";
    this["context"].fillRect(0, 0, this["canvas"].clientWidth, this["canvas"].clientHeight);

    var mapVisibleColumns = this["mapVisibleColumns"];
    var mapVisibleRows = this["mapVisibleRows"];
    var bac = this["bac"];

    if (this["assetBackground"].src && bac && typeof bac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleColumns + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = bac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawBackgroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawForeground = function() {
    var mapVisibleColumns = this["mapVisibleColumns"];
    var mapVisibleRows = this["mapVisibleRows"];
    var fac = this["fac"];

    if (this["assetForeground"].src && fac && typeof fac === "function") {
        for (var i = this.viewOffsetI; i < mapVisibleColumns + this.viewOffsetI; i++) {
            for (var j = this.viewOffsetJ; j < mapVisibleRows + this.viewOffsetJ; j++) {
                var coor = fac(i, j);
                if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
                    this.drawForegroundTile(coor.x, coor.y, i, j);
                }
            }
        }
    }
};

MapFactory.prototype.drawCursor = function (i, j) {
    var cac = this["cac"];

    if (this["assetCursor"].src && cac && typeof cac === "function") {
        var coor = cac(i + this.viewOffsetI, j + this.viewOffsetJ);
        if (coor && typeof coor.x === 'number' && typeof coor.y === 'number') {
            this.drawCursorTile(coor.x, coor.y, i, j);
        }
    }
};

MapFactory.prototype.drawBackgroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetBackground"];

    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.drawForegroundTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetForeground"];
    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.drawCursorTile = function (sourceX, sourceY, i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    var asset = this["assetCursor"];
    this["context"].drawImage(asset,
        sourceX, sourceY, this.tileWidth, this.tileHeight, destX, destY, this.tileWidth, this.tileHeight);
};

MapFactory.prototype.clearTile = function (i, j) {
    var destX = (i - this.viewOffsetI) * this.tileWidth;
    var destY = (j - this.viewOffsetJ) * this.tileHeight;
    this["context"].clearRect(destX, destY, this.tileWidth, this.tileHeight);
};
<|endoftext|>


<commit_before>/*
Mag.JS AddOns v0.23.3
(c) Michael Glazer 2017
https://github.com/magnumjs/mag.js
Requires: MagJS (core) Addons: Ajax , Router
*/
;
(function(mag, window, document, undefined) {

  'use strict';

  //Create wrapper for function call to mag.module with over riding default props
  mag.create = function(id, module, props) {
    return function(id2, props2) {
      if (typeof id2 !== 'string' && !mag.utils.isHTMLElment(id2)) {
        props2 = id2
        id2 = 0
      }
      return mag.module(id2 || id, module, mag.merge(mag.copy(props) || {}, mag.copy(props2) || {}))
    }
  }

  mag.template = function(url, cb) {
    return mag.request(url)
      .then(function(data) {

        var template = mag.doc.createElement('template');
        template.innerHTML = data;
        document.body.appendChild(template);

	      cb && cb(template.content.children[0])

        return template.content.children[0];
      })
  }

  mag.getTags = function(tagName) {
    var parentID = mag.utils.items.getItemVal(mag.mod.runningViewInstance);
    var parentNode = mag.getNode(parentID);
    var nodes = (parentNode || mag.doc).getElementsByTagName(tagName);

    var elements = [].slice.call(nodes);
    return elements;
  }

  //wrapper around mag.module|mag.create for getting node(s) by tagName
  mag.tag = function(tagName, module, props, type) {
    var elements = mag.getTags(tagName);
    var instances = [];
    type = type || 'module' // create?

    if (elements.length) {
      for (var node of elements) {
        instances.push(mag[type](node, module, props));
      }
    }
    return instances.length == 1 ? instances[0] : instances;
  }

  //UTILITY
  // mag.utils.copy - now in core
  // mag.utils.merge - now in core

  // helper
  mag.noop = function() {}
  mag.copy = mag.utils.copy
  mag.merge = mag.utils.merge

  mag.isEmpty = function(x) {
    // undefined, '', null, 0, empty array
    if (!x || 0 == x.length) return true
      // empty {}
    if ('Object]' == x.toString().substr(-7)) {
      for (var n in x)
        if (x.hasOwnProperty(n)) return false
      return true
    }
    //TODO:? convert to string and match single non-space character
    return x.trim()
  };

  /*
controller:function(props)
	// if props has a property 'isVisible' when true will display entire module on before updates
	mag.show(this,'isVisible')
}
http://jsbin.com/fegizubime/edit?html,js,output
*/

  // show hide

  mag.show = function(context, conditionName) {
    var ctype = typeof context;
    if (ctype !== 'object') {
      var obj = {
        _config: function(node) {
          node.style.display = context ? 'block' : 'none'
        }
      }
      if (typeof conditionName == 'object') {
        mag.merge(conditionName, obj)
      }
      return obj
    } else if (ctype == 'object') {
      context.willload = function(node, inprops) {
        node.style.display = 'block'
      }
    } else {
      context.willupdate = function(node, inprops) {
        node.style.display = inprops[conditionName] ? 'block' : 'none'
      }
    }
  }

  // promises

  /*
var d = mag.deferred()

d.promise.then(function() {
	console.log('success', arguments)
}, function() {
	console.log('failure', arguments)
})
d.reject({
	things: false
})
d.resolve({
	things: []
})
*/

  mag.deferred = function() {
    var Deferred = {}
    Deferred.promise = new Promise(function(resolve, reject) {
      Deferred.resolve = resolve
      Deferred.reject = reject
    })
    return Deferred
  }

  mag.when = function(arrayOfPromises, callback) {
    return Promise.all(arrayOfPromises).then(callback)
  }

  //TODO: add jsonp support

  mag.request = function(options) {
    if (typeof options == 'string') {
      var url = options;
      options = {}
      options.url = url;
    }
    var deferred = mag.deferred();
    if (options.initialValue) {
      deferred.promise.initialValue = options.initialValue
    }
    //Uid:
    var key = JSON.stringify(options)
      //Cache:
    if (options.cache) {
      var cache = mag.cache(key)
      if (cache) {
        deferred.resolve(cache)
        return deferred.promise
      }
    }

    //In queue:
    if (mag.request.queue[key]) {
      return mag.request.queue[key]
    }
    //Add to queue
    mag.request.queue[key] = deferred.promise;

    var client = new XMLHttpRequest();
    var method = (options.method || 'GET').toUpperCase();
    var data = method === "GET" || !options.data ? "" : typeof options.data === 'object' ? JSON.stringify(options.data) : options.data

    client.onload = function(e) {
      var ct = client.getResponseHeader("content-type") || "";
      var data = e.target.responseText;
      if (ct.indexOf('json') > -1) {
        data = JSON.parse(data)
      }
      if (options.cache) {
        mag.cache(key, data, options.cacheTime, options.cacheType)
      }
      delete mag.request.queue[key];
      var headers = client.getAllResponseHeaders();
      deferred.resolve(data, headers);
    }

    client.onerror = function(e) {
      deferred.reject(e)
    };

    client.open(method, options.url);
    if (options.withCredentials) client.withCredentials = options.withCredentials

    //Headers:
    if (options.headers) {
      for (var i = 0; i < options.headers.length; i++) {
        var header = options.headers[i]
        for (var k in header) {
          client.setRequestHeader(k, header[k]);
        }
      }
    }
    client.send(data);

    return deferred.promise
  }
  mag.request.queue = {}

  mag.cache = function(key, data, cacheTime, storageType) {
    var stype = window[storageType || 'sessionStorage']

    if (arguments.length == 1) {

      if (mag.cache.data[key]) {
        return mag.cache.data[key].data;
      } else {
        // check storage
        var item = stype.getItem(key)
        if (item) {
          mag.cache.data[key] = JSON.parse(item)
          return mag.cache.data[key].data
        } else return 0;
      }
    }

    if (mag.cache.data[key] && mag.cache.data[key].id) clearTimeout(mag.cache.data[key].id)

    var intervalID = setTimeout(function(key) {
      stype.removeItem(key);
      delete mag.cache.data[key]
    }.bind({}, key), cacheTime || 1000 * 60 * 10); //10 minutes

    mag.cache.data[key] = {
      id: intervalID,
      data: data
    }

    stype.setItem(key, JSON.stringify(mag.cache.data[key]))
  }
  mag.cache.data = {}

  // goes with mag.request for json ajax requests
  /*
will show and hide all ''.loader' elements in transition
*/
  mag.requestWithFeedback = function(args) {
    var key = JSON.stringify(args)
    if (!mag.requestWithFeedback.cache[key]) {

      var loaders = document.querySelectorAll(".loader")

      //show icons
      for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "block"

      var expire = function(data) {
        delete mag.requestWithFeedback.cache[key]

        if (Object.keys(mag.requestWithFeedback.cache).length == 0) {
          //hide icons
          for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "none"
        }
        return data
      }
      mag.requestWithFeedback.cache[key] = mag.request(args)
      mag.requestWithFeedback.cache[key].then(expire, function(error) {
        expire(error)
        throw error
      })
    }
    return mag.requestWithFeedback.cache[key]
  }
  mag.requestWithFeedback.cache = {}

  // disable add to config
  // http://jsbin.com/zepacabile/edit?js,output
  // define condition func
  // this.show = mag.prop(false);

  //define context
  //   this.input = mag.disable(this.show)
  // OR mag.disable(this.show, this.input={})
  //TODO: change to mag.ui.disable
  mag.disable = function(condition, obj) {

    // add or merge to config if not exists
    var orig = obj && obj._config ? obj._config : function() {};

    var tmp = {
      _config: function(n) {
        orig.apply(this, arguments);
        if (condition()) n.setAttribute('disabled', 'disabled');
        else n.removeAttribute('disabled');
      }
    }
    if (obj) {
      mag.merge(obj, tmp);
    }
    return tmp;

  }

  // TODO: change to mag.find
  // find a MagJS element Matcher
  mag.find = function(parentRootId, selector) {
    var parent = document.getElementById(parentRootId);
    return mag.fill.find(parent, selector)
  }

  // mag.addons.toMenu(['one', 'two', 'three'], state.selected())
  // http://jsbin.com/mapuwojumu/3/edit?js,output
  //TODO change to mag.ui.menu
  mag.toMenu = function(maps, selected) {
    return maps.map(function(v, k) {
      return {
        _text: v,
        _value: k,
        _selected: selected === k || selected === v ? true : null
      }
    })
  }

  /*
module library creation with single global namespace / package names
(function(namespace) {

	var mod = {
		view: function(state, props) {
		}
	}

	namespace.CommentBox = mod;

})(mag.namespace('mglazer.mods.comments'));
*/
  mag.namespace = function(ns, obj) {
    var levels = ns.split('.'),
      first = levels.shift();
    obj = obj || this; //if no object argument supplied declare a global property
    obj[first] = obj[first] || {}; // initialize the "level"
    if (levels.length) { // recursion condition
      return mag.namespace(levels.join('.'), obj[first]);
    }
    return obj[first]; // return a reference to the top level object
  }

  //ROUTER:

  mag.route = (function(window) {

    'use strict';

    var routes = [];

    function addRoute(route, handler) {

      routes.push({
        parts: route.split('/'),
        handler: handler
      });
    }

    function load(route) {
      window.location.hash = route;
    }

    function start() {

      var path = window.location.hash.substr(1),
        parts = path.split('/'),
        partsLength = parts.length;

      for (var i = 0; i < routes.length; i++) {
        var route = routes[i];
        if (route.parts.length === partsLength) {
          var params = [];
          for (var j = 0; j < partsLength; j++) {
            if (route.parts[j].substr(0, 1) === ':') {
              params.push(parts[j]);
            } else if (route.parts[j] !== parts[j]) {
              break;
            }
          }
          if (j === partsLength) {
            route.handler.apply(undefined, params);
            return;
          }
        }
      }
    }

    window.onhashchange = start;

    return {
      addRoute: addRoute,
      load: load,
      start: start
    };

  }(window));

  //PLUGINS!
  // hookins

  function forEach(obj, fn) {
    var keys = Object.keys(obj);
    for (var i = 0, l = keys.length; i < l; ++i) {
      var key = keys[i];
      fn(key, obj[key]);
    }
  }

  mag.hookin('attributes', 'key', function(data) {
    // remove system key from being added to attributes in html
    data.value = null
  });

  function camelDash(val) {
    return val.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  mag.hookin('attributes', 'style', function(data) {

    //convert from object
    if (typeof data.value === 'object') {
      // get current styles not overwrite existing?

      var ostyle = data.node.getAttribute('style') || '';

      // make sure not already in styles
      var nstyle = ''
      forEach(data.value, function(key, value) {
        nstyle += camelDash(key) + ':' + (!isNaN(value) ? (value + "px") : value) + ';';
      });

      if (ostyle && ~ostyle.indexOf(nstyle)) data.value = ostyle;
      else data.value = ostyle + nstyle;

    }
  });

  // _className plugin example
  mag.hookin('attributes', 'className', function(data) {
    data.key = 'class';

    var newClass = data.value
    var origClass = data.node.classList + ''

    if (typeof data.value == 'string') {
      newClass = data.value.split(' ')
    } else if (typeof data.value == 'object') {
      for (var k in newClass) {
        data.node.classList.toggle(k, newClass[k])
      }
      data.value = data.node.classList + ''
      return
    }

    data.value = data.node.classList + ''

    for (var k in newClass) {
      var cls = newClass[k]
      if (cls.trim() && data.node.classList.length > 0 && !data.node.classList.contains(cls.trim())) {
        data.value = data.node.classList + ' ' + cls

      } else if (cls.trim() && data.node.classList.length == 0) {
        data.value += cls + ' '
      }
    }

    if (data.value.trim() !== origClass) {
      data.change = 1;
    }
    data.value = data.value.trim()
  })

  var queue = [];
  //Values hookin:
  mag.hookin('values', '*', function(data) {
    var dtype = typeof data.value

    if (data.value == null) {

    } else
    // allow function to return values
    if (dtype === 'function' && data.value.toJSON) {
      data.change = 1
      data.value = data.value()
    } else
    // Allow for promises to be resolved
    if (dtype == 'object' && typeof data.value.then == 'function') {
      if (!queue[mag.fill.id + data.key]) {
        queue[mag.fill.id + data.key] = 1;
        data.value.then(function(fillId, dataKey, newData) {
          if (fillId) {
            var mid = mag.utils.items.getItem(fillId);

            if (~mid) {
              mag.mod.getState(mid)[dataKey] = newData;
              mag.redraw(mag.getNode(fillId), mid);
              queue[fillId + data.key] = 0;
            }
          }
          return newData;
        }.bind(null, mag.fill.id, data.key));
      }
      if (data.value.initialValue) {
        data.change = 1;
        data.value = data.value.initialValue;
      }
    }
  });

  //GetId helper method
  mag.getId = function(instanceId) {
    return mag.utils.items.getItemVal(instanceId);
  };

  mag.prop = function(store) {
    var prop = function() {
      if (arguments.length) store = arguments[0];
      return store;
    };

    prop.toJSON = function() {
      return store;
    };

    return prop;
  };

  mag.withProp = function(prop, withAttrCallback) {
    return function(e) {
      e = e || event;
      var currentTarget = e.currentTarget || this;
      withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop))
    }
  }

})(mag, window || global || this, document);
<commit_msg>Update mag.addons.0.2.js<commit_after>/*
Mag.JS AddOns v0.23.3
(c) Michael Glazer 2017
https://github.com/magnumjs/mag.js
Requires: MagJS (core) Addons: Ajax , Router
*/
;
(function(mag, window, document, undefined) {

  'use strict';

  //Create wrapper for function call to mag.module with over riding default props
  mag.create = function(id, module, props) {
    return function(id2, props2) {
      if (typeof id2 !== 'string' && !mag.utils.isHTMLElment(id2)) {
        props2 = id2
        id2 = 0
      }
      return mag.module(id2 || id, module, mag.merge(mag.copy(props) || {}, mag.copy(props2) || {}))
    }
  }

  mag.template = function(url, cb) {
    return mag.request(url)
      .then(function(data) {

        var template = mag.doc.createElement('template');
        template.innerHTML = data;
        document.body.appendChild(template);

        cb && cb(template.content.children[0])

        return template.content.children[0];
      })
  }

  mag.getTags = function(tagName) {
    var parentID = mag.utils.items.getItemVal(mag.mod.runningViewInstance);
    var parentNode = mag.getNode(parentID);
    var nodes = (parentNode || mag.doc).getElementsByTagName(tagName);

    var elements = [].slice.call(nodes);
    return elements;
  }

  //wrapper around mag.module|mag.create for getting node(s) by tagName
  mag.tag = function(tagName, module, props, type) {
    var elements = mag.getTags(tagName);
    var instances = [];
    type = type || 'module' // create?

    if (elements.length) {
      for (var node of elements) {
        instances.push(mag[type](node, module, props));
      }
    }
    return instances.length == 1 ? instances[0] : instances;
  }

  //UTILITY
  // mag.utils.copy - now in core
  // mag.utils.merge - now in core

  // helper
  mag.noop = function() {}
  mag.copy = mag.utils.copy
  mag.merge = mag.utils.merge

  mag.isEmpty = function(x) {
    // undefined, '', null, 0, empty array
    if (!x || 0 == x.length) return true
      // empty {}
    if ('Object]' == x.toString().substr(-7)) {
      for (var n in x)
        if (x.hasOwnProperty(n)) return false
      return true
    }
    //TODO:? convert to string and match single non-space character
    return x.trim()
  };

  /*
controller:function(props)
	// if props has a property 'isVisible' when true will display entire module on before updates
	mag.show(this,'isVisible')
}
http://jsbin.com/fegizubime/edit?html,js,output
*/

  // show hide

  mag.show = function(context, conditionName) {
    var ctype = typeof context;
    if (ctype !== 'object') {
      var obj = {
        _config: function(node) {
          node.style.display = context ? 'block' : 'none'
        }
      }
      if (typeof conditionName == 'object') {
        mag.merge(conditionName, obj)
      }
      return obj
    } else if (ctype == 'object') {
      context.willload = function(node, inprops) {
        node.style.display = 'block'
      }
    } else {
      context.willupdate = function(node, inprops) {
        node.style.display = inprops[conditionName] ? 'block' : 'none'
      }
    }
  }

  // promises

  /*
var d = mag.deferred()

d.promise.then(function() {
	console.log('success', arguments)
}, function() {
	console.log('failure', arguments)
})
d.reject({
	things: false
})
d.resolve({
	things: []
})
*/

  mag.deferred = function() {
    var Deferred = {}
    Deferred.promise = new Promise(function(resolve, reject) {
      Deferred.resolve = resolve
      Deferred.reject = reject
    })
    return Deferred
  }

  mag.when = function(arrayOfPromises, callback) {
    return Promise.all(arrayOfPromises).then(callback)
  }

  //TODO: add jsonp support

  mag.request = function(options) {
    if (typeof options == 'string') {
      var url = options;
      options = {}
      options.url = url;
    }
    var deferred = mag.deferred();
    if (options.initialValue) {
      deferred.promise.initialValue = options.initialValue
    }
    //Uid:
    var key = JSON.stringify(options)
      //Cache:
    if (options.cache) {
      var cache = mag.cache(key)
      if (cache) {
        deferred.resolve(cache)
        return deferred.promise
      }
    }

    //In queue:
    if (mag.request.queue[key]) {
      return mag.request.queue[key]
    }
    //Add to queue
    mag.request.queue[key] = deferred.promise;

    var client = new XMLHttpRequest();
    var method = (options.method || 'GET').toUpperCase();
    var data = method === "GET" || !options.data ? "" : typeof options.data === 'object' ? JSON.stringify(options.data) : options.data

    client.onload = function(e) {
      var ct = client.getResponseHeader("content-type") || "";
      var data = e.target.responseText;
      if (ct.indexOf('json') > -1) {
        data = JSON.parse(data)
      }
      if (options.cache) {
        mag.cache(key, data, options.cacheTime, options.cacheType)
      }
      delete mag.request.queue[key];
      var headers = client.getAllResponseHeaders();
      deferred.resolve(data, headers);
    }

    client.onerror = function(e) {
      deferred.reject(e)
    };

    client.open(method, options.url);
    if (options.withCredentials) client.withCredentials = options.withCredentials

    //Headers:
    if (options.headers) {
      for (var i = 0; i < options.headers.length; i++) {
        var header = options.headers[i]
        for (var k in header) {
          client.setRequestHeader(k, header[k]);
        }
      }
    }
    client.send(data);

    return deferred.promise
  }
  mag.request.queue = {}

  mag.cache = function(key, data, cacheTime, storageType) {
    var stype = window[storageType || 'sessionStorage']

    if (arguments.length == 1) {

      if (mag.cache.data[key]) {
        return mag.cache.data[key].data;
      } else {
        // check storage
        var item = stype.getItem(key)
        if (item) {
          mag.cache.data[key] = JSON.parse(item)
          return mag.cache.data[key].data
        } else return 0;
      }
    }

    if (mag.cache.data[key] && mag.cache.data[key].id) clearTimeout(mag.cache.data[key].id)

    var intervalID = setTimeout(function(key) {
      stype.removeItem(key);
      delete mag.cache.data[key]
    }.bind({}, key), cacheTime || 1000 * 60 * 10); //10 minutes

    mag.cache.data[key] = {
      id: intervalID,
      data: data
    }

    stype.setItem(key, JSON.stringify(mag.cache.data[key]))
  }
  mag.cache.data = {}

  // goes with mag.request for json ajax requests
  /*
will show and hide all ''.loader' elements in transition
*/
  mag.requestWithFeedback = function(args) {
    var key = JSON.stringify(args)
    if (!mag.requestWithFeedback.cache[key]) {

      var loaders = document.querySelectorAll(".loader")

      //show icons
      for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "block"

      var expire = function(data) {
        delete mag.requestWithFeedback.cache[key]

        if (Object.keys(mag.requestWithFeedback.cache).length == 0) {
          //hide icons
          for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "none"
        }
        return data
      }
      mag.requestWithFeedback.cache[key] = mag.request(args)
      mag.requestWithFeedback.cache[key].then(expire, function(error) {
        expire(error)
        throw error
      })
    }
    return mag.requestWithFeedback.cache[key]
  }
  mag.requestWithFeedback.cache = {}

  // disable add to config
  // http://jsbin.com/zepacabile/edit?js,output
  // define condition func
  // this.show = mag.prop(false);

  //define context
  //   this.input = mag.disable(this.show)
  // OR mag.disable(this.show, this.input={})
  //TODO: change to mag.ui.disable
  mag.disable = function(condition, obj) {

    // add or merge to config if not exists
    var orig = obj && obj._config ? obj._config : function() {};

    var tmp = {
      _config: function(n) {
        orig.apply(this, arguments);
        if (condition()) n.setAttribute('disabled', 'disabled');
        else n.removeAttribute('disabled');
      }
    }
    if (obj) {
      mag.merge(obj, tmp);
    }
    return tmp;

  }

  // TODO: change to mag.find
  // find a MagJS element Matcher
  mag.find = function(parentRootId, selector) {
    var parent = document.getElementById(parentRootId);
    return mag.fill.find(parent, selector)
  }

  // mag.addons.toMenu(['one', 'two', 'three'], state.selected())
  // http://jsbin.com/mapuwojumu/3/edit?js,output
  //TODO change to mag.ui.menu
  mag.toMenu = function(maps, selected) {
    return maps.map(function(v, k) {
      return {
        _text: v,
        _value: k,
        _selected: selected === k || selected === v ? true : null
      }
    })
  }

  /*
module library creation with single global namespace / package names
(function(namespace) {

	var mod = {
		view: function(state, props) {
		}
	}

	namespace.CommentBox = mod;

})(mag.namespace('mglazer.mods.comments'));
*/
  mag.namespace = function(ns, obj) {
    var levels = ns.split('.'),
      first = levels.shift();
    obj = obj || this; //if no object argument supplied declare a global property
    obj[first] = obj[first] || {}; // initialize the "level"
    if (levels.length) { // recursion condition
      return mag.namespace(levels.join('.'), obj[first]);
    }
    return obj[first]; // return a reference to the top level object
  }

  //ROUTER:

  mag.route = (function(window) {

    'use strict';

    var routes = [];

    function addRoute(route, handler) {

      routes.push({
        parts: route.split('/'),
        handler: handler
      });
    }

    function load(route) {
      window.location.hash = route;
    }

    function start() {

      var path = window.location.hash.substr(1),
        parts = path.split('/'),
        partsLength = parts.length;

      for (var i = 0; i < routes.length; i++) {
        var route = routes[i];
        if (route.parts.length === partsLength) {
          var params = [];
          for (var j = 0; j < partsLength; j++) {
            if (route.parts[j].substr(0, 1) === ':') {
              params.push(parts[j]);
            } else if (route.parts[j] !== parts[j]) {
              break;
            }
          }
          if (j === partsLength) {
            route.handler.apply(undefined, params);
            return;
          }
        }
      }
    }

    window.onhashchange = start;

    return {
      addRoute: addRoute,
      load: load,
      start: start
    };

  }(window));

  //PLUGINS!
  // hookins

  function forEach(obj, fn) {
    var keys = Object.keys(obj);
    for (var i = 0, l = keys.length; i < l; ++i) {
      var key = keys[i];
      fn(key, obj[key]);
    }
  }

  mag.hookin('attributes', 'key', function(data) {
    // remove system key from being added to attributes in html
    data.value = null
  });

  function camelDash(val) {
    return val.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  mag.hookin('attributes', 'style', function(data) {

    //convert from object
    if (typeof data.value === 'object') {
      // get current styles not overwrite existing?

      var ostyle = data.node.getAttribute('style') || '';

      // make sure not already in styles
      var nstyle = ''
      forEach(data.value, function(key, value) {
        nstyle += camelDash(key) + ':' + (!isNaN(value) ? (value + "px") : value) + ';';
      });

      if (ostyle && ~ostyle.indexOf(nstyle)) data.value = ostyle;
      else data.value = ostyle + nstyle;

    }
  });

  // _className plugin example
  mag.hookin('attributes', 'className', function(data) {
    data.key = 'class';

    var newClass = data.value
    var origClass = data.node.classList + ''

    if (typeof data.value == 'string') {
      newClass = data.value.split(' ')
    } else if (typeof data.value == 'object') {
      for (var k in newClass) {
        data.node.classList.toggle(k, newClass[k])
      }
      data.value = data.node.classList + ''
      return
    }

    data.value = data.node.classList + ''

    for (var k in newClass) {
      var cls = newClass[k]
      if (cls.trim() && data.node.classList.length > 0 && !data.node.classList.contains(cls.trim())) {
        data.value = data.node.classList + ' ' + cls

      } else if (cls.trim() && data.node.classList.length == 0) {
        data.value += cls + ' '
      }
    }

    if (data.value.trim() !== origClass) {
      data.change = 1;
    }
    data.value = data.value.trim()
  })

  var queue = [];
  //Values hookin:
  mag.hookin('values', '*', function(data) {
    var dtype = typeof data.value

    if (data.value == null) {

    } else
    // allow function to return values
    if (dtype === 'function' && data.value.toJSON) {
      data.change = 1
      data.value = data.value()
    } else
    // Allow for promises to be resolved
    if (dtype == 'object' && typeof data.value.then == 'function') {
      if (!queue[mag.fill.id + data.key]) {
        queue[mag.fill.id + data.key] = 1;
        data.value.then(function(fillId, dataKey, newData) {
          if (fillId) {
            var mid = mag.utils.items.getItem(fillId);

            if (~mid) {
              mag.mod.getState(mid)[dataKey] = newData;
              mag.redraw(mag.getNode(fillId), mid);
              queue[fillId + data.key] = 0;
            }
          }
          return newData;
        }.bind(null, mag.fill.id, data.key));
      }
      if (data.value.initialValue) {
        data.change = 1;
        data.value = data.value.initialValue;
      }
    }
  });

  //GetId helper method
  mag.getId = function(instanceId) {
    return mag.utils.items.getItemVal(instanceId);
  };

  mag.prop = function(store) {
    var prop = function() {
      if (arguments.length) store = arguments[0];
      return store;
    };

    prop.toJSON = function() {
      return store;
    };

    return prop;
  };

  mag.withProp = function(prop, withAttrCallback) {
    return function(e) {
      e = e || event;
      var currentTarget = e.currentTarget || this;
      withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop))
    }
  }

})(mag, window || global || this, document);
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc service
 * @name $rootElement
 *
 * @description
 * The root element of Angular application. This is either the element where {@link
 * ng.directive:ngApp ngApp} was declared or the element passed into
 * {@link angular.bootstrap}. The element represent the root element of application. It is also the
 * location where the applications {@link auto.$injector $injector} service gets
 * published, it can be retrieved using `$rootElement.injector()`.
 */

// the implementation is in angular.bootstrap
<commit_msg>docs($rootElement): fix minor grammatical errors<commit_after>'use strict';

/**
 * @ngdoc service
 * @name $rootElement
 *
 * @description
 * The root element of Angular application. This is either the element where {@link
 * ng.directive:ngApp ngApp} was declared or the element passed into
 * {@link angular.bootstrap}. The element represents the root element of application. It is also the
 * location where the application's {@link auto.$injector $injector} service gets
 * published, and can be retrieved using `$rootElement.injector()`.
 */

// the implementation is in angular.bootstrap
<|endoftext|>


<commit_before>/* globals ShadyCSS */
window.MagnonElement = class extends HTMLElement {
    constructor(options = { shadow: true }) {
        super();

        this.usesShadow = options.shadow;
        this.usesShadyCSS = options.shadow && ShadyCSS;

        const searchForTemplate = (doc) => {
            const template = doc.querySelector(`#${this.name}`);
            if (template) return template;
            else {
                for (const link of doc.querySelectorAll("link[rel=import]")) {
                    const template = searchForTemplate(link.import);
                    if (template) return template;
                }
            }
        };

        const t = searchForTemplate(document);
        if (!t) return; // Component without elements

        if (this.usesShadyCSS) ShadyCSS.prepareTemplate(t, this.name);

        this._instance = t.content.cloneNode(true);
        if (this.usesShadyCSS) ShadyCSS.styleElement(this._instance);

        this.root = options.shadow ? this.attachShadow({ mode: "open" }) : this;
        this.root.appendChild(this._instance);
    }

    static get name() {
        throw new TypeError("Element is missing an element name declaration");
    }

    get name() {
        return this.constructor.name;
    }

    static init() {
        customElements.define(this.name, this);
    }

    static restyle() {
        if (this.usesShadyCSS) ShadyCSS.styleDocument();
    }

    restyleLocal() {
        if (this.usesShadyCSS) ShadyCSS.styleSubtree(this._instance);
    }

    fire(eventName, detail) {
        this.dispatchEvent(new CustomEvent(eventName, {
            detail
        }));
    }
};
<commit_msg>Don't run shadyCSS if shadow dom isn't present<commit_after>window.MagnonElement = class extends HTMLElement {
    constructor(options = { shadow: true }) {
        super();

        this.usesShadow = options.shadow;
        this.usesShadyCSS = options.shadow && window.ShadyCSS;

        const searchForTemplate = (doc) => {
            const template = doc.querySelector(`#${this.name}`);
            if (template) return template;
            else {
                for (const link of doc.querySelectorAll("link[rel=import]")) {
                    const template = searchForTemplate(link.import);
                    if (template) return template;
                }
            }
        };

        const t = searchForTemplate(document);
        if (!t) return; // Component without elements

        if (this.usesShadyCSS) window.ShadyCSS.prepareTemplate(t, this.name);

        this._instance = t.content.cloneNode(true);
        if (this.usesShadyCSS) window.ShadyCSS.styleElement(this._instance);

        this.root = options.shadow ? this.attachShadow({ mode: "open" }) : this;
        this.root.appendChild(this._instance);
    }

    static get name() {
        throw new TypeError("Element is missing an element name declaration");
    }

    get name() {
        return this.constructor.name;
    }

    static init() {
        customElements.define(this.name, this);
    }

    static restyle() {
        if (this.usesShadyCSS) window.ShadyCSS.styleDocument();
    }

    restyleLocal() {
        if (this.usesShadyCSS) window.ShadyCSS.styleSubtree(this._instance);
    }

    fire(eventName, detail) {
        this.dispatchEvent(new CustomEvent(eventName, {
            detail
        }));
    }
};
<|endoftext|>