<commit_before>define(['elaiJS/configuration', 'elaiJS/binder'],
          function(config, binder) {
	'use strict';

	var self = {};
	binder.addFunctions(self);

  var EVENT = {
    beforeUnload:           "beforeUnload",
    beforeUnloadInternal:   "beforeUnloadInternal",
    beforeUnloadNavigator:  "beforeUnloadNavigator",
    pageChanged:            "pageChanged",
    reload:                 "reload"
  };
	self.EVENT = EVENT;

  var ignoreHasChange = false;
  var beforeUnloadCallback;
	var beforeUnloadMessage;
	var beforeUnloadShowMessage;
	var currentPageInfo;

	self.initialize = function initialize() {
		window.onhashchange = function() {
		  if(ignoreHasChange)
		    return ignoreHasChange = false;

		  if(beforeUnloadMessage) {
		    fire(EVENT.beforeUnload);
		    fire(EVENT.beforeUnloadInternal);
		    setTimeout(showInternalBeforeUnloadMessage());
		    return;
		  }

		  changeCurrentPage();
		};

		initializeCurrentPage();
	};

	function changeCurrentPage() {
    var oldPageInfo = currentPageInfo;
    initializeCurrentPage();
    fire(EVENT.pageChanged, currentPageInfo, oldPageInfo);
	}

	function fire(type, newPageInfo, oldPageInfo) {
		var event = {
		  type: type,
		  newPageInfo: newPageInfo,
		  oldPageInfo: oldPageInfo
		};

		self.fire(event);
	}

	function initializeCurrentPage() {
    currentPageInfo = buidCurrentPageInfo();
	}

	self.getCurrentPageInfo = function getCurrentPageInfo() {
	  return currentPageInfo;
	};

	self.goTo = function goTo(pageInfo) {
		location.hash = buildHash(pageInfo);
	};

	self.forward = function forward() {
		window.history.forward();
	};

	self.back = function back() {
		window.history.back();
	};

	self.reload = function reload() {
		fire(EVENT.reload, currentPageInfo);
	}

	self.getHistoryState = function getHistoryState(state) {
    return window.history.state;
	};

	self.saveHistoryState = function saveHistoryState(state) {
    window.history.pushState(state, "");
	};

	self.addToHistoryState = function addToHistoryState(params) {
    var state = self.getHistoryState();
	  if(!state)
	    state = {};

    for(var key in params)
      state[key] = params[key];

    self.saveHistoryState(state);
	};

	function buildHash(pageInfo) {
    if(!config.elaiJS.buildHash)
      throw new Error("Missing configuration for 'elaiJS.buildHash'.");

    return config.elaiJS.buildHash(pageInfo);
	}

	function buidCurrentPageInfo() {
    if(!config.elaiJS.extractPageInfo)
      throw new Error("Missing configuration for 'elaiJS.extractPageInfo'.");

    var hash = location.hash.substring(1);
    return config.elaiJS.extractPageInfo(hash);
	}

	function showInternalBeforeUnloadMessage() {
	  var message = getBeforeUnloadMessage(true);
    if(!message)
      return changeCurrentPage();

    var action = config.elaiJS.showInternalBeforeUnloadMessage;
    if(beforeUnloadShowMessage)
      action = beforeUnloadShowMessage;

    action(message, function(continueNav) {
      if(continueNav) {
        changeCurrentPage();
        self.removeBeforeUnloadMessage();
      }
      else {
        ignoreHasChange = true;
        self.back();
      }
    });
  }

	self.setBeforeUnloadMessage = function setBeforeUnloadMessage(message, callback, showMessage) {
	  beforeUnloadMessage = message;
	  beforeUnloadCallback = callback;
	  beforeUnloadShowMessage = showMessage;

		window.onbeforeunload = function() {
		  fire(EVENT.beforeUnload);
		  fire(EVENT.beforeUnloadNavigator);
		  return getBeforeUnloadMessage(false);
		};
	};

	self.removeBeforeUnloadMessage = function removeBeforeUnloadMessage() {
		window.onbeforeunload = undefined;
		beforeUnloadCallback = undefined;
		beforeUnloadMessage = undefined;
		beforeUnloadShowMessage = undefined;
	};

	function getBeforeUnloadMessage(isInternal) {
    if(beforeUnloadCallback)
      return beforeUnloadCallback(beforeUnloadMessage, isInternal);

    if(config.elaiJS.beforeUnloadCallback)
      return config.elaiJS.beforeUnloadCallback(beforeUnloadMessage, isInternal);

    return beforeUnloadMessage;
	}

	self.addCookie = function(arg1, arg2) {
	  var params = (arg2) ? {name: arg1, value: arg2} : arg1;

	  if(params.remove !== false)
      self.removeCookie(params.name);

    var date = params.expires;
    if(!date && params.days) {
      date = new Date();
      date.setTime(date.getTime() + (params.days*24*60*60*1000));
    }

    var cookieStr = params.name + "=" + params.value + ";";
    if(date)
      cookieStr += "expires=" + date.toUTCString() + ";";
    if(params.path)
      cookieStr += "path=" + params.path + ";";

    document.cookie = cookieStr;
    return self;
	};

	self.getCookie = function(name) {
    return self.getCookies()[name];
	};

	self.removeCookie = function(name) {
	  var date = new Date();
	  date.setTime(date.getTime() - 1);

    return self.addCookie({name: name, value: "", expires: date, remove: false});
	};

	self.getCookies = function() {
	  // It's better for health when it's raw !
    var rawCookies = document.cookie.split(';');
    var cookies = {};
    for(var i in rawCookies) {
      var rawCookie = rawCookies[i].trim();
      if(rawCookie !== "") {
        rawCookie = rawCookie.split("=");
        cookies[rawCookie[0]] = rawCookie[1];
      }
    }

    return cookies;
	};

	self.removeAllCookies = function() {
    for(var name in self.getCookies())
      self.removeCookie(name);
	};

	return self;
});<commit_msg>Change pushState to replaceState for historyState<commit_after>define(['elaiJS/configuration', 'elaiJS/binder'],
          function(config, binder) {
	'use strict';

	var self = {};
	binder.addFunctions(self);

  var EVENT = {
    beforeUnload:           "beforeUnload",
    beforeUnloadInternal:   "beforeUnloadInternal",
    beforeUnloadNavigator:  "beforeUnloadNavigator",
    pageChanged:            "pageChanged",
    reload:                 "reload"
  };
	self.EVENT = EVENT;

  var ignoreHasChange = false;
  var beforeUnloadCallback;
	var beforeUnloadMessage;
	var beforeUnloadShowMessage;
	var currentPageInfo;

	self.initialize = function initialize() {
		window.onhashchange = function() {
		  if(ignoreHasChange)
		    return ignoreHasChange = false;

		  if(beforeUnloadMessage) {
		    fire(EVENT.beforeUnload);
		    fire(EVENT.beforeUnloadInternal);
		    setTimeout(showInternalBeforeUnloadMessage());
		    return;
		  }

		  changeCurrentPage();
		};

		initializeCurrentPage();
	};

	function changeCurrentPage() {
    var oldPageInfo = currentPageInfo;
    initializeCurrentPage();
    fire(EVENT.pageChanged, currentPageInfo, oldPageInfo);
	}

	function fire(type, newPageInfo, oldPageInfo) {
		var event = {
		  type: type,
		  newPageInfo: newPageInfo,
		  oldPageInfo: oldPageInfo
		};

		self.fire(event);
	}

	function initializeCurrentPage() {
    currentPageInfo = buidCurrentPageInfo();
	}

	self.getCurrentPageInfo = function getCurrentPageInfo() {
	  return currentPageInfo;
	};

	self.goTo = function goTo(pageInfo) {
		location.hash = buildHash(pageInfo);
	};

	self.forward = function forward() {
		window.history.forward();
	};

	self.back = function back() {
		window.history.back();
	};

	self.reload = function reload() {
		fire(EVENT.reload, currentPageInfo);
	}

	self.getHistoryState = function getHistoryState() {
    return window.history.state;
	};

	self.saveHistoryState = function saveHistoryState(state) {
    window.history.replaceState(state, "");
	};

	self.addToHistoryState = function addToHistoryState(params) {
    var state = self.getHistoryState();
	  if(!state)
	    state = {};

    for(var key in params)
      state[key] = params[key];

    self.saveHistoryState(state);
	};

	function buildHash(pageInfo) {
    if(!config.elaiJS.buildHash)
      throw new Error("Missing configuration for 'elaiJS.buildHash'.");

    return config.elaiJS.buildHash(pageInfo);
	}

	function buidCurrentPageInfo() {
    if(!config.elaiJS.extractPageInfo)
      throw new Error("Missing configuration for 'elaiJS.extractPageInfo'.");

    var hash = location.hash.substring(1);
    return config.elaiJS.extractPageInfo(hash);
	}

	function showInternalBeforeUnloadMessage() {
	  var message = getBeforeUnloadMessage(true);
    if(!message)
      return changeCurrentPage();

    var action = config.elaiJS.showInternalBeforeUnloadMessage;
    if(beforeUnloadShowMessage)
      action = beforeUnloadShowMessage;

    action(message, function(continueNav) {
      if(continueNav) {
        changeCurrentPage();
        self.removeBeforeUnloadMessage();
      }
      else {
        ignoreHasChange = true;
        self.back();
      }
    });
  }

	self.setBeforeUnloadMessage = function setBeforeUnloadMessage(message, callback, showMessage) {
	  beforeUnloadMessage = message;
	  beforeUnloadCallback = callback;
	  beforeUnloadShowMessage = showMessage;

		window.onbeforeunload = function() {
		  fire(EVENT.beforeUnload);
		  fire(EVENT.beforeUnloadNavigator);
		  return getBeforeUnloadMessage(false);
		};
	};

	self.removeBeforeUnloadMessage = function removeBeforeUnloadMessage() {
		window.onbeforeunload = undefined;
		beforeUnloadCallback = undefined;
		beforeUnloadMessage = undefined;
		beforeUnloadShowMessage = undefined;
	};

	function getBeforeUnloadMessage(isInternal) {
    if(beforeUnloadCallback)
      return beforeUnloadCallback(beforeUnloadMessage, isInternal);

    if(config.elaiJS.beforeUnloadCallback)
      return config.elaiJS.beforeUnloadCallback(beforeUnloadMessage, isInternal);

    return beforeUnloadMessage;
	}

	self.addCookie = function(arg1, arg2) {
	  var params = (arg2) ? {name: arg1, value: arg2} : arg1;

	  if(params.remove !== false)
      self.removeCookie(params.name);

    var date = params.expires;
    if(!date && params.days) {
      date = new Date();
      date.setTime(date.getTime() + (params.days*24*60*60*1000));
    }

    var cookieStr = params.name + "=" + params.value + ";";
    if(date)
      cookieStr += "expires=" + date.toUTCString() + ";";
    if(params.path)
      cookieStr += "path=" + params.path + ";";

    document.cookie = cookieStr;
    return self;
	};

	self.getCookie = function(name) {
    return self.getCookies()[name];
	};

	self.removeCookie = function(name) {
	  var date = new Date();
	  date.setTime(date.getTime() - 1);

    return self.addCookie({name: name, value: "", expires: date, remove: false});
	};

	self.getCookies = function() {
	  // It's better for health when it's raw !
    var rawCookies = document.cookie.split(';');
    var cookies = {};
    for(var i in rawCookies) {
      var rawCookie = rawCookies[i].trim();
      if(rawCookie !== "") {
        rawCookie = rawCookie.split("=");
        cookies[rawCookie[0]] = rawCookie[1];
      }
    }

    return cookies;
	};

	self.removeAllCookies = function() {
    for(var name in self.getCookies())
      self.removeCookie(name);
	};

	return self;
});<|endoftext|>


<commit_before>/**
 * @file Tag generation helper
 * @since 0.2.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfIsArray*/ // Return true if the parameter is an array
/*global _gpfIsLiteralObject*/ // Check if the parameter is a literal object
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*global _gpfStringEscapeFor*/ // Make the string content compatible with lang
/*global _gpfSyncReadSourceJSON*/ // Reads a source json file (only in source mode)
/*#endif*/

/**
 * Tag instance allocated by {@link gpf.typedef.tagFunc}
 *
 * @class gpf.typedef.Tag
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * String conversion
 *
 * @method gpf.typedef.Tag.prototype.toString
 * @return {String} HTML result
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * DOM insertion
 *
 * @method gpf.typedef.Tag.prototype.appendTo
 * @param {Object} node DOM parent to append the node to
 * @return {Object} Created DOM Node
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function child specification.  When string, a text node is created.
 *
 * @typedef gpf.typedef.tagChild
 * @property {String|gpf.typedef.Tag}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function children specification
 *
 * @typedef gpf.typedef.tagChildren
 * @property {gpf.typedef.tagChild|gpf.typedef.tagChild[]}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function, it accepts up to two parameters:
 * - When no parameter is passed, an empty node is created
 * - If the first parameter is a literal object (see {@link gpf.isLiteralObject}), its properties are used to
 *   define the node attributes
 * - Otherwise, the second (or first if not a literal object) is used to define children that will be appended to this
 *   tag
 *
 * @callback gpf.typedef.tagFunc
 *
 * @param {Object|gpf.typedef.tagChildren} [attributes=undefined] When a literal object is passed, it is interpreted
 * as an attribute dictionary. The attribute name may contain the xlink namespace prefix.
 * @param {gpf.typedef.tagChildren} [children=undefined] List of children
 * @return {gpf.typedef.Tag} Tag object
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

_gpfErrorDeclare("web/tag", {
    /**
     * ### Summary
     *
     * Missing node name
     *
     * ### Description
     *
     * A tag can't be created if the node name is missing
     * @since 0.2.1
     */
    missingNodeName: "Missing node name",

    /**
     * ### Summary
     *
     * Unknown namespace prefix
     *
     * ### Description
     *
     * A prefix has been used prior to be associated with a namespace
     * @since 0.2.2
     */
    unknownNamespacePrefix: "Unknown namespace prefix",

    /**
     * ### Summary
     *
     * Unable to use namespace in string
     *
     * ### Description
     *
     * A prefix associated to a namespace has been used and can't be converted to string
     * @since 0.2.2
     */
    unableToUseNamespaceInString: "Unable to use namespace in string"
});

/**
 * Mapping of attribute name aliases
 * @type {Object}
 * @since 0.2.1
 */
var _gpfWebTagAttributeAliases = _gpfSyncReadSourceJSON("web/attributes.json");

/**
 * Mapping of prefixes for namespaces
 * @type {Object}
 * @since 0.2.2
 */
var _gpfWebNamespacePrefix = _gpfSyncReadSourceJSON("web/namespaces.json");

/**
 * Retrieves namespace associated to the prefix or fail
 *
 * @param {String} prefix Namespace prefix
 * @return {String} Namespace URI
 * @throws {gpf.Error.UnknownNamespacePrefix}
 * @since 0.2.2
 */
function _gpfWebGetNamespace (prefix) {
    var namespace = _gpfWebNamespacePrefix[prefix];
    if (undefined === namespace) {
        gpf.Error.unknownNamespacePrefix();
    }
    return namespace;
}

/**
 * Resolves prefixed name to namespace and name
 *
 * @param {String} name Attribute or node name
 * @return {{namespace, name}|undefined} Namespace and name in a structure if prefixed, undefined otherwise
 * @since 0.2.2
 */
function _gpfWebGetNamespaceAndName (name) {
    var parts = name.split(":");
    if (parts.length === 2) {
        return {
            namespace: _gpfWebGetNamespace(parts[0]),
            name: parts[1]
        };
    }
}

/**
 * Fails if the name includes namespace prefix
 *
 * @param {String} name Attribute or node name
 * @throws {gpf.Error.UnableToUseNamespaceInString}
 * @since 0.2.2
 */
function _gpfWebCheckNamespaceSafe (name) {
    if (name.indexOf(":") !== -1) {
        gpf.Error.unableToUseNamespaceInString();
    }
}

/**
 * Resolve attribute name
 *
 * @param {String} name Attribute name used in the tag function
 * @return {String} Attribute to set on the node ele,ment
 * @since 0.2.1
 */
function _gpfWebTagAttributeAlias (name) {
    return _gpfWebTagAttributeAliases[name] || name;
}

/**
 * Apply the callback to each array item,
 * process recursively if the array item is an array
 *
 * @param {Array} array array of items
 * @param {Function} callback Function to apply on each array item
 * @since 0.2.1
 */
function _gpfWebTagFlattenChildren (array, callback) {
    array.forEach(function (item) {
        if (_gpfIsArray(item)) {
            _gpfWebTagFlattenChildren(item, callback);
        } else {
            callback(item);
        }
    });
}

var _GpfWebTag = _gpfDefine({
    $class: "gpf.web.Tag",

    /**
     * Tag object
     *
     * @param {String} nodeName Node name
     * @param {Object} [attributes] Dictionary of attributes to set
     * @param {Array} [children] Children
     *
     * @constructor gpf.web.Tag
     * @private
     * @since 0.2.1
     */
    constructor: function (nodeName, attributes, children) {
        this._nodeName = nodeName;
        this._attributes = attributes || {};
        this._children = children;
    },

    /**
     * Node name
     * @since 0.2.1
     */
    _nodeName: "",

    /**
     * Node attributes
     * @since 0.2.1
     */
    _attributes: {},

    /**
     * Node children
     * @since 0.2.1
     */
    _children: [],

    //region toString implementation

    _getAttributesAsString: function () {
        return Object.keys(this._attributes).map(function (name) {
            _gpfWebCheckNamespaceSafe(name);
            return " " + _gpfWebTagAttributeAlias(name)
                + "=\"" + _gpfStringEscapeFor(this._attributes[name], "html") + "\"";
        }, this).join("");
    },

    _getChildrenAsString: function () {
        var result = [];
        _gpfWebTagFlattenChildren(this._children, function (child) {
            result.push(child.toString());
        });
        return result.join("");
    },

    _getClosingString: function () {
        if (this._children.length) {
            return ">" + this._getChildrenAsString() + "</" + this._nodeName + ">";
        }
        return "/>";
    },

    /**
     * Convert the current tag into HTML
     *
     * @return {String} HTML representation of the tag
     * @since 0.2.1
     */
    toString: function () {
        _gpfWebCheckNamespaceSafe(this._nodeName);
        return "<" + this._nodeName + this._getAttributesAsString() + this._getClosingString();
    },

    //endregion

    //region appendTo implementation

    _createElement: function (node) {
        var ownerDocument = node.ownerDocument,
            qualified = _gpfWebGetNamespaceAndName(this._nodeName);
        if (qualified) {
            return ownerDocument.createElementNS(qualified.namespace, qualified.name);
        }
        return ownerDocument.createElement(this._nodeName);
    },

    _setAttributesTo: function (node) {
        _gpfObjectForEach(this._attributes, function (value, name) {
            var qualified = _gpfWebGetNamespaceAndName(name);
            if (qualified) {
                node.setAttributeNS(qualified.namespace, _gpfWebTagAttributeAlias(qualified.name), value);
            } else {
                node.setAttribute(_gpfWebTagAttributeAlias(name), value);
            }
        });
    },

    _appendChildrenTo: function (node) {
        var ownerDocument = node.ownerDocument;
        _gpfWebTagFlattenChildren(this._children, function (child) {
            if (child instanceof _GpfWebTag) {
                child.appendTo(node);
            } else {
                node.appendChild(ownerDocument.createTextNode(child.toString()));
            }
        });
    },

    /**
     * Appends the tag to the provided node
     *
     * @param {Object} node Expected to be a DOM node
     * @return {Object} Created node
     * @since 0.2.1
     */
    appendTo: function (node) {
        var element = this._createElement(node);
        this._setAttributesTo(element);
        this._appendChildrenTo(element);
        return node.appendChild(element);
    }

    //endregion

});

/**
 * Create a tag generation function
 *
 * @param {String} nodeName tag name.
 * May include the namespace prefix svg for [SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG)
 * @return {gpf.typedef.tagFunc} The tag generation function
 * @gpf:closure
 * @since 0.2.1
 */
function _gpfWebTagCreateFunction (nodeName) {
    if (!nodeName) {
        gpf.Error.missingNodeName();
    }
    return function (firstParam) {
        var sliceFrom,
            attributes;
        if (_gpfIsLiteralObject(firstParam)) {
            attributes = firstParam;
            sliceFrom = 1;
        } else {
            sliceFrom = 0;
        }
        return new _GpfWebTag(nodeName, attributes, [].slice.call(arguments, sliceFrom));
    };
}

/**
 * @gpf:sameas _gpfWebTagCreateFunction
 * @since 0.2.1
 *
 * @example
Tree building to string

 * var div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 * tree = div({className: "test1"}, "Hello ", span({className: "test2"}, "World!"));
 * // tree.toString() gives
Hello World!

 *
 * @example
Tree building to DOM

 * var mockNode = mockDocument.createElement("any"),
 *     div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 *     tree = div({className: "test"}, "Hello ", span("World!")),
 *     result = tree.appendTo(mockNode);
 *
 * @example
SVG building

 * var mockNode = mockDocument.createElement("any"),
 *     svgImage = gpf.web.createTagFunction("svg:image"),
 *     tree = svgImage({x: 0, y: 0, "xlink:href": "test.png"}),
 *     result = tree.appendTo(mockNode);
 *
 */
gpf.web.createTagFunction = _gpfWebTagCreateFunction;
<commit_msg>no-magic-numbers (#280)<commit_after>/**
 * @file Tag generation helper
 * @since 0.2.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfIsArray*/ // Return true if the parameter is an array
/*global _gpfIsLiteralObject*/ // Check if the parameter is a literal object
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*global _gpfStringEscapeFor*/ // Make the string content compatible with lang
/*global _gpfSyncReadSourceJSON*/ // Reads a source json file (only in source mode)
/*#endif*/

/**
 * Tag instance allocated by {@link gpf.typedef.tagFunc}
 *
 * @class gpf.typedef.Tag
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * String conversion
 *
 * @method gpf.typedef.Tag.prototype.toString
 * @return {String} HTML result
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * DOM insertion
 *
 * @method gpf.typedef.Tag.prototype.appendTo
 * @param {Object} node DOM parent to append the node to
 * @return {Object} Created DOM Node
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function child specification.  When string, a text node is created.
 *
 * @typedef gpf.typedef.tagChild
 * @property {String|gpf.typedef.Tag}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function children specification
 *
 * @typedef gpf.typedef.tagChildren
 * @property {gpf.typedef.tagChild|gpf.typedef.tagChild[]}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function, it accepts up to two parameters:
 * - When no parameter is passed, an empty node is created
 * - If the first parameter is a literal object (see {@link gpf.isLiteralObject}), its properties are used to
 *   define the node attributes
 * - Otherwise, the second (or first if not a literal object) is used to define children that will be appended to this
 *   tag
 *
 * @callback gpf.typedef.tagFunc
 *
 * @param {Object|gpf.typedef.tagChildren} [attributes=undefined] When a literal object is passed, it is interpreted
 * as an attribute dictionary. The attribute name may contain the xlink namespace prefix.
 * @param {gpf.typedef.tagChildren} [children=undefined] List of children
 * @return {gpf.typedef.Tag} Tag object
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

_gpfErrorDeclare("web/tag", {
    /**
     * ### Summary
     *
     * Missing node name
     *
     * ### Description
     *
     * A tag can't be created if the node name is missing
     * @since 0.2.1
     */
    missingNodeName: "Missing node name",

    /**
     * ### Summary
     *
     * Unknown namespace prefix
     *
     * ### Description
     *
     * A prefix has been used prior to be associated with a namespace
     * @since 0.2.2
     */
    unknownNamespacePrefix: "Unknown namespace prefix",

    /**
     * ### Summary
     *
     * Unable to use namespace in string
     *
     * ### Description
     *
     * A prefix associated to a namespace has been used and can't be converted to string
     * @since 0.2.2
     */
    unableToUseNamespaceInString: "Unable to use namespace in string"
});

/**
 * Mapping of attribute name aliases
 * @type {Object}
 * @since 0.2.1
 */
var _gpfWebTagAttributeAliases = _gpfSyncReadSourceJSON("web/attributes.json");

/**
 * Mapping of prefixes for namespaces
 * @type {Object}
 * @since 0.2.2
 */
var _gpfWebNamespacePrefix = _gpfSyncReadSourceJSON("web/namespaces.json");

/**
 * Retrieves namespace associated to the prefix or fail
 *
 * @param {String} prefix Namespace prefix
 * @return {String} Namespace URI
 * @throws {gpf.Error.UnknownNamespacePrefix}
 * @since 0.2.2
 */
function _gpfWebGetNamespace (prefix) {
    var namespace = _gpfWebNamespacePrefix[prefix];
    if (undefined === namespace) {
        gpf.Error.unknownNamespacePrefix();
    }
    return namespace;
}

/**
 * Resolves prefixed name to namespace and name
 *
 * @param {String} name Attribute or node name
 * @return {{namespace, name}|undefined} Namespace and name in a structure if prefixed, undefined otherwise
 * @since 0.2.2
 */
function _gpfWebGetNamespaceAndName (name) {
    var EXPECTED_PARTS_COUNT = 2,
        NAMESPACE_PREFIX = 0,
        NAME = 1,
        parts = name.split(":");
    if (parts.length === EXPECTED_PARTS_COUNT) {
        return {
            namespace: _gpfWebGetNamespace(parts[NAMESPACE_PREFIX]),
            name: parts[NAME]
        };
    }
}

/**
 * Fails if the name includes namespace prefix
 *
 * @param {String} name Attribute or node name
 * @throws {gpf.Error.UnableToUseNamespaceInString}
 * @since 0.2.2
 */
function _gpfWebCheckNamespaceSafe (name) {
    if (name.indexOf(":") !== -1) {
        gpf.Error.unableToUseNamespaceInString();
    }
}

/**
 * Resolve attribute name
 *
 * @param {String} name Attribute name used in the tag function
 * @return {String} Attribute to set on the node ele,ment
 * @since 0.2.1
 */
function _gpfWebTagAttributeAlias (name) {
    return _gpfWebTagAttributeAliases[name] || name;
}

/**
 * Apply the callback to each array item,
 * process recursively if the array item is an array
 *
 * @param {Array} array array of items
 * @param {Function} callback Function to apply on each array item
 * @since 0.2.1
 */
function _gpfWebTagFlattenChildren (array, callback) {
    array.forEach(function (item) {
        if (_gpfIsArray(item)) {
            _gpfWebTagFlattenChildren(item, callback);
        } else {
            callback(item);
        }
    });
}

var _GpfWebTag = _gpfDefine({
    $class: "gpf.web.Tag",

    /**
     * Tag object
     *
     * @param {String} nodeName Node name
     * @param {Object} [attributes] Dictionary of attributes to set
     * @param {Array} [children] Children
     *
     * @constructor gpf.web.Tag
     * @private
     * @since 0.2.1
     */
    constructor: function (nodeName, attributes, children) {
        this._nodeName = nodeName;
        this._attributes = attributes || {};
        this._children = children;
    },

    /**
     * Node name
     * @since 0.2.1
     */
    _nodeName: "",

    /**
     * Node attributes
     * @since 0.2.1
     */
    _attributes: {},

    /**
     * Node children
     * @since 0.2.1
     */
    _children: [],

    //region toString implementation

    _getAttributesAsString: function () {
        return Object.keys(this._attributes).map(function (name) {
            _gpfWebCheckNamespaceSafe(name);
            return " " + _gpfWebTagAttributeAlias(name)
                + "=\"" + _gpfStringEscapeFor(this._attributes[name], "html") + "\"";
        }, this).join("");
    },

    _getChildrenAsString: function () {
        var result = [];
        _gpfWebTagFlattenChildren(this._children, function (child) {
            result.push(child.toString());
        });
        return result.join("");
    },

    _getClosingString: function () {
        if (this._children.length) {
            return ">" + this._getChildrenAsString() + "</" + this._nodeName + ">";
        }
        return "/>";
    },

    /**
     * Convert the current tag into HTML
     *
     * @return {String} HTML representation of the tag
     * @since 0.2.1
     */
    toString: function () {
        _gpfWebCheckNamespaceSafe(this._nodeName);
        return "<" + this._nodeName + this._getAttributesAsString() + this._getClosingString();
    },

    //endregion

    //region appendTo implementation

    _createElement: function (node) {
        var ownerDocument = node.ownerDocument,
            qualified = _gpfWebGetNamespaceAndName(this._nodeName);
        if (qualified) {
            return ownerDocument.createElementNS(qualified.namespace, qualified.name);
        }
        return ownerDocument.createElement(this._nodeName);
    },

    _setAttributesTo: function (node) {
        _gpfObjectForEach(this._attributes, function (value, name) {
            var qualified = _gpfWebGetNamespaceAndName(name);
            if (qualified) {
                node.setAttributeNS(qualified.namespace, _gpfWebTagAttributeAlias(qualified.name), value);
            } else {
                node.setAttribute(_gpfWebTagAttributeAlias(name), value);
            }
        });
    },

    _appendChildrenTo: function (node) {
        var ownerDocument = node.ownerDocument;
        _gpfWebTagFlattenChildren(this._children, function (child) {
            if (child instanceof _GpfWebTag) {
                child.appendTo(node);
            } else {
                node.appendChild(ownerDocument.createTextNode(child.toString()));
            }
        });
    },

    /**
     * Appends the tag to the provided node
     *
     * @param {Object} node Expected to be a DOM node
     * @return {Object} Created node
     * @since 0.2.1
     */
    appendTo: function (node) {
        var element = this._createElement(node);
        this._setAttributesTo(element);
        this._appendChildrenTo(element);
        return node.appendChild(element);
    }

    //endregion

});

/**
 * Create a tag generation function
 *
 * @param {String} nodeName tag name.
 * May include the namespace prefix svg for [SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG)
 * @return {gpf.typedef.tagFunc} The tag generation function
 * @gpf:closure
 * @since 0.2.1
 */
function _gpfWebTagCreateFunction (nodeName) {
    if (!nodeName) {
        gpf.Error.missingNodeName();
    }
    return function (firstParam) {
        var sliceFrom,
            attributes;
        if (_gpfIsLiteralObject(firstParam)) {
            attributes = firstParam;
            sliceFrom = 1;
        } else {
            sliceFrom = 0;
        }
        return new _GpfWebTag(nodeName, attributes, [].slice.call(arguments, sliceFrom));
    };
}

/**
 * @gpf:sameas _gpfWebTagCreateFunction
 * @since 0.2.1
 *
 * @example
Tree building to string

 * var div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 * tree = div({className: "test1"}, "Hello ", span({className: "test2"}, "World!"));
 * // tree.toString() gives
Hello World!

 *
 * @example
Tree building to DOM

 * var mockNode = mockDocument.createElement("any"),
 *     div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 *     tree = div({className: "test"}, "Hello ", span("World!")),
 *     result = tree.appendTo(mockNode);
 *
 * @example
SVG building

 * var mockNode = mockDocument.createElement("any"),
 *     svgImage = gpf.web.createTagFunction("svg:image"),
 *     tree = svgImage({x: 0, y: 0, "xlink:href": "test.png"}),
 *     result = tree.appendTo(mockNode);
 *
 */
gpf.web.createTagFunction = _gpfWebTagCreateFunction;
<|endoftext|>


<commit_before>'use strict';

!function($) {

/**
 * Orbit module.
 * @module foundation.orbit
 * @requires foundation.util.keyboard
 * @requires foundation.util.motion
 * @requires foundation.util.timerAndImageLoader
 * @requires foundation.util.touch
 */

class Orbit {
  /**
  * Creates a new instance of an orbit carousel.
  * @class
  * @param {jQuery} element - jQuery object to make into an Orbit Carousel.
  * @param {Object} options - Overrides to the default plugin settings.
  */
  constructor(element, options){
    this.$element = element;
    this.options = $.extend({}, Orbit.defaults, this.$element.data(), options);

    this._init();

    Foundation.registerPlugin(this, 'Orbit');
    Foundation.Keyboard.register('Orbit', {
      'ltr': {
        'ARROW_RIGHT': 'next',
        'ARROW_LEFT': 'previous'
      },
      'rtl': {
        'ARROW_LEFT': 'next',
        'ARROW_RIGHT': 'previous'
      }
    });
  }

  /**
  * Initializes the plugin by creating jQuery collections, setting attributes, and starting the animation.
  * @function
  * @private
  */
  _init() {
    this.$wrapper = this.$element.find(`.${this.options.containerClass}`);
    this.$slides = this.$element.find(`.${this.options.slideClass}`);
    var $images = this.$element.find('img'),
    initActive = this.$slides.filter('.is-active');

    if (!initActive.length) {
      this.$slides.eq(0).addClass('is-active');
    }

    if (!this.options.useMUI) {
      this.$slides.addClass('no-motionui');
    }

    if ($images.length) {
      Foundation.onImagesLoaded($images, this._prepareForOrbit.bind(this));
    } else {
      this._prepareForOrbit();//hehe
    }

    if (this.options.bullets) {
      this._loadBullets();
    }

    this._events();

    if (this.options.autoPlay && this.$slides.length > 1) {
      this.geoSync();
    }

    if (this.options.accessible) { // allow wrapper to be focusable to enable arrow navigation
      this.$wrapper.attr('tabindex', 0);
    }
  }

  /**
  * Creates a jQuery collection of bullets, if they are being used.
  * @function
  * @private
  */
  _loadBullets() {
    this.$bullets = this.$element.find(`.${this.options.boxOfBullets}`).find('button');
  }

  /**
  * Sets a `timer` object on the orbit, and starts the counter for the next slide.
  * @function
  */
  geoSync() {
    var _this = this;
    this.timer = new Foundation.Timer(
      this.$element,
      {
        duration: this.options.timerDelay,
        infinite: false
      },
      function() {
        _this.changeSlide(true);
      });
    this.timer.start();
  }

  /**
  * Sets wrapper and slide heights for the orbit.
  * @function
  * @private
  */
  _prepareForOrbit() {
    var _this = this;
    this._setWrapperHeight(function(max){
      _this._setSlideHeight(max);
    });
  }

  /**
  * Calulates the height of each slide in the collection, and uses the tallest one for the wrapper height.
  * @function
  * @private
  * @param {Function} cb - a callback function to fire when complete.
  */
  _setWrapperHeight(cb) {//rewrite this to `for` loop
    var max = 0, temp, counter = 0;

    this.$slides.each(function() {
      temp = this.getBoundingClientRect().height;
      $(this).attr('data-slide', counter);

      if (counter) {//if not the first slide, set css position and display property
        $(this).css({'position': 'relative', 'display': 'none'});
      }
      max = temp > max ? temp : max;
      counter++;
    });

    if (counter === this.$slides.length) {
      this.$wrapper.css({'height': max}); //only change the wrapper height property once.
      cb(max); //fire callback with max height dimension.
    }
  }

  /**
  * Sets the max-height of each slide.
  * @function
  * @private
  */
  _setSlideHeight(height) {
    this.$slides.each(function() {
      $(this).css('max-height', height);
    });
  }

  /**
  * Adds event listeners to basically everything within the element.
  * @function
  * @private
  */
  _events() {
    var _this = this;

    //***************************************
    //**Now using custom event - thanks to:**
    //**      Yohai Ararat of Toronto      **
    //***************************************
    if (this.$slides.length > 1) {

      if (this.options.swipe) {
        this.$slides.off('swipeleft.zf.orbit swiperight.zf.orbit')
        .on('swipeleft.zf.orbit', function(e){
          e.preventDefault();
          _this.changeSlide(true);
        }).on('swiperight.zf.orbit', function(e){
          e.preventDefault();
          _this.changeSlide(false);
        });
      }
      //***************************************

      if (this.options.autoPlay) {
        this.$slides.on('click.zf.orbit', function() {
          _this.$element.data('clickedOn', _this.$element.data('clickedOn') ? false : true);
          _this.timer[_this.$element.data('clickedOn') ? 'pause' : 'start']();
        });

        if (this.options.pauseOnHover) {
          this.$element.on('mouseenter.zf.orbit', function() {
            _this.timer.pause();
          }).on('mouseleave.zf.orbit', function() {
            if (!_this.$element.data('clickedOn')) {
              _this.timer.start();
            }
          });
        }
      }

      if (this.options.navButtons) {
        var $controls = this.$element.find(`.${this.options.nextClass}, .${this.options.prevClass}`);
        $controls.attr('tabindex', 0)
        //also need to handle enter/return and spacebar key presses
        .on('click.zf.orbit touchend.zf.orbit', function(){
          _this.changeSlide($(this).hasClass(_this.options.nextClass));
        });
      }

      if (this.options.bullets) {
        this.$bullets.on('click.zf.orbit touchend.zf.orbit', function() {
          if (/is-active/g.test(this.className)) { return false; }//if this is active, kick out of function.
          var idx = $(this).data('slide'),
          ltr = idx > _this.$slides.filter('.is-active').data('slide'),
          $slide = _this.$slides.eq(idx);

          _this.changeSlide(ltr, $slide, idx);
        });
      }

      this.$wrapper.add(this.$bullets).on('keydown.zf.orbit', function(e) {
        // handle keyboard event with keyboard util
        Foundation.Keyboard.handleKey(e, 'Orbit', {
          next: function() {
            _this.changeSlide(true);
          },
          previous: function() {
            _this.changeSlide(false);
          },
          handled: function() { // if bullet is focused, make sure focus moves
            if ($(e.target).is(_this.$bullets)) {
              _this.$bullets.filter('.is-active').focus();
            }
          }
        });
      });
    }
  }

  /**
  * Changes the current slide to a new one.
  * @function
  * @param {Boolean} isLTR - flag if the slide should move left to right.
  * @param {jQuery} chosenSlide - the jQuery element of the slide to show next, if one is selected.
  * @param {Number} idx - the index of the new slide in its collection, if one chosen.
  * @fires Orbit#slidechange
  */
  changeSlide(isLTR, chosenSlide, idx) {
    var $curSlide = this.$slides.filter('.is-active').eq(0);

    if (/mui/g.test($curSlide[0].className)) { return false; } //if the slide is currently animating, kick out of the function

    var $firstSlide = this.$slides.first(),
    $lastSlide = this.$slides.last(),
    dirIn = isLTR ? 'Right' : 'Left',
    dirOut = isLTR ? 'Left' : 'Right',
    _this = this,
    $newSlide;

    if (!chosenSlide) { //most of the time, this will be auto played or clicked from the navButtons.
      $newSlide = isLTR ? //if wrapping enabled, check to see if there is a `next` or `prev` sibling, if not, select the first or last slide to fill in. if wrapping not enabled, attempt to select `next` or `prev`, if there's nothing there, the function will kick out on next step. CRAZY NESTED TERNARIES!!!!!
      (this.options.infiniteWrap ? $curSlide.next(`.${this.options.slideClass}`).length ? $curSlide.next(`.${this.options.slideClass}`) : $firstSlide : $curSlide.next(`.${this.options.slideClass}`))//pick next slide if moving left to right
      :
      (this.options.infiniteWrap ? $curSlide.prev(`.${this.options.slideClass}`).length ? $curSlide.prev(`.${this.options.slideClass}`) : $lastSlide : $curSlide.prev(`.${this.options.slideClass}`));//pick prev slide if moving right to left
    } else {
      $newSlide = chosenSlide;
    }

    if ($newSlide.length) {
      if (this.options.bullets) {
        idx = idx || this.$slides.index($newSlide); //grab index to update bullets
        this._updateBullets(idx);
      }

      if (this.options.useMUI) {
        Foundation.Motion.animateIn(
          $newSlide.addClass('is-active').css({'position': 'absolute', 'top': 0}),
          this.options[`animInFrom${dirIn}`],
          function(){
            $newSlide.css({'position': 'relative', 'display': 'block'})
            .attr('aria-live', 'polite');
        });

        Foundation.Motion.animateOut(
          $curSlide.removeClass('is-active'),
          this.options[`animOutTo${dirOut}`],
          function(){
            $curSlide.removeAttr('aria-live');
            if(_this.options.autoPlay && !_this.timer.isPaused){
              _this.timer.restart();
            }
            //do stuff?
          });
      } else {
        $curSlide.removeClass('is-active is-in').removeAttr('aria-live').hide();
        $newSlide.addClass('is-active is-in').attr('aria-live', 'polite').show();
        if (this.options.autoPlay && !this.timer.isPaused) {
          this.timer.restart();
        }
      }
    /**
    * Triggers when the slide has finished animating in.
    * @event Orbit#slidechange
    */
      this.$element.trigger('slidechange.zf.orbit', [$newSlide]);
    }
  }

  /**
  * Updates the active state of the bullets, if displayed.
  * @function
  * @private
  * @param {Number} idx - the index of the current slide.
  */
  _updateBullets(idx) {
    var $oldBullet = this.$element.find(`.${this.options.boxOfBullets}`)
    .find('.is-active').removeClass('is-active').blur(),
    span = $oldBullet.find('span:last').detach(),
    $newBullet = this.$bullets.eq(idx).addClass('is-active').append(span);
  }

  /**
  * Destroys the carousel and hides the element.
  * @function
  */
  destroy() {
    this.$element.off('.zf.orbit').find('*').off('.zf.orbit').end().hide();
    Foundation.unregisterPlugin(this);
  }
}

Orbit.defaults = {
  /**
  * Tells the JS to look for and loadBullets.
  * @option
  * @example true
  */
  bullets: true,
  /**
  * Tells the JS to apply event listeners to nav buttons
  * @option
  * @example true
  */
  navButtons: true,
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-in-right'
  */
  animInFromRight: 'slide-in-right',
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-out-right'
  */
  animOutToRight: 'slide-out-right',
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-in-left'
  *
  */
  animInFromLeft: 'slide-in-left',
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-out-left'
  */
  animOutToLeft: 'slide-out-left',
  /**
  * Allows Orbit to automatically animate on page load.
  * @option
  * @example true
  */
  autoPlay: true,
  /**
  * Amount of time, in ms, between slide transitions
  * @option
  * @example 5000
  */
  timerDelay: 5000,
  /**
  * Allows Orbit to infinitely loop through the slides
  * @option
  * @example true
  */
  infiniteWrap: true,
  /**
  * Allows the Orbit slides to bind to swipe events for mobile, requires an additional util library
  * @option
  * @example true
  */
  swipe: true,
  /**
  * Allows the timing function to pause animation on hover.
  * @option
  * @example true
  */
  pauseOnHover: true,
  /**
  * Allows Orbit to bind keyboard events to the slider, to animate frames with arrow keys
  * @option
  * @example true
  */
  accessible: true,
  /**
  * Class applied to the container of Orbit
  * @option
  * @example 'orbit-container'
  */
  containerClass: 'orbit-container',
  /**
  * Class applied to individual slides.
  * @option
  * @example 'orbit-slide'
  */
  slideClass: 'orbit-slide',
  /**
  * Class applied to the bullet container. You're welcome.
  * @option
  * @example 'orbit-bullets'
  */
  boxOfBullets: 'orbit-bullets',
  /**
  * Class applied to the `next` navigation button.
  * @option
  * @example 'orbit-next'
  */
  nextClass: 'orbit-next',
  /**
  * Class applied to the `previous` navigation button.
  * @option
  * @example 'orbit-previous'
  */
  prevClass: 'orbit-previous',
  /**
  * Boolean to flag the js to use motion ui classes or not. Default to true for backwards compatability.
  * @option
  * @example true
  */
  useMUI: true
};

// Window exports
Foundation.plugin(Orbit, 'Orbit');

}(jQuery);
<commit_msg>fix orbit navigation<commit_after>'use strict';

!function($) {

/**
 * Orbit module.
 * @module foundation.orbit
 * @requires foundation.util.keyboard
 * @requires foundation.util.motion
 * @requires foundation.util.timerAndImageLoader
 * @requires foundation.util.touch
 */

class Orbit {
  /**
  * Creates a new instance of an orbit carousel.
  * @class
  * @param {jQuery} element - jQuery object to make into an Orbit Carousel.
  * @param {Object} options - Overrides to the default plugin settings.
  */
  constructor(element, options){
    this.$element = element;
    this.options = $.extend({}, Orbit.defaults, this.$element.data(), options);

    this._init();

    Foundation.registerPlugin(this, 'Orbit');
    Foundation.Keyboard.register('Orbit', {
      'ltr': {
        'ARROW_RIGHT': 'next',
        'ARROW_LEFT': 'previous'
      },
      'rtl': {
        'ARROW_LEFT': 'next',
        'ARROW_RIGHT': 'previous'
      }
    });
  }

  /**
  * Initializes the plugin by creating jQuery collections, setting attributes, and starting the animation.
  * @function
  * @private
  */
  _init() {
    this.$wrapper = this.$element.find(`.${this.options.containerClass}`);
    this.$slides = this.$element.find(`.${this.options.slideClass}`);
    var $images = this.$element.find('img'),
    initActive = this.$slides.filter('.is-active');

    if (!initActive.length) {
      this.$slides.eq(0).addClass('is-active');
    }

    if (!this.options.useMUI) {
      this.$slides.addClass('no-motionui');
    }

    if ($images.length) {
      Foundation.onImagesLoaded($images, this._prepareForOrbit.bind(this));
    } else {
      this._prepareForOrbit();//hehe
    }

    if (this.options.bullets) {
      this._loadBullets();
    }

    this._events();

    if (this.options.autoPlay && this.$slides.length > 1) {
      this.geoSync();
    }

    if (this.options.accessible) { // allow wrapper to be focusable to enable arrow navigation
      this.$wrapper.attr('tabindex', 0);
    }
  }

  /**
  * Creates a jQuery collection of bullets, if they are being used.
  * @function
  * @private
  */
  _loadBullets() {
    this.$bullets = this.$element.find(`.${this.options.boxOfBullets}`).find('button');
  }

  /**
  * Sets a `timer` object on the orbit, and starts the counter for the next slide.
  * @function
  */
  geoSync() {
    var _this = this;
    this.timer = new Foundation.Timer(
      this.$element,
      {
        duration: this.options.timerDelay,
        infinite: false
      },
      function() {
        _this.changeSlide(true);
      });
    this.timer.start();
  }

  /**
  * Sets wrapper and slide heights for the orbit.
  * @function
  * @private
  */
  _prepareForOrbit() {
    var _this = this;
    this._setWrapperHeight(function(max){
      _this._setSlideHeight(max);
    });
  }

  /**
  * Calulates the height of each slide in the collection, and uses the tallest one for the wrapper height.
  * @function
  * @private
  * @param {Function} cb - a callback function to fire when complete.
  */
  _setWrapperHeight(cb) {//rewrite this to `for` loop
    var max = 0, temp, counter = 0;

    this.$slides.each(function() {
      temp = this.getBoundingClientRect().height;
      $(this).attr('data-slide', counter);

      if (counter) {//if not the first slide, set css position and display property
        $(this).css({'position': 'relative', 'display': 'none'});
      }
      max = temp > max ? temp : max;
      counter++;
    });

    if (counter === this.$slides.length) {
      this.$wrapper.css({'height': max}); //only change the wrapper height property once.
      cb(max); //fire callback with max height dimension.
    }
  }

  /**
  * Sets the max-height of each slide.
  * @function
  * @private
  */
  _setSlideHeight(height) {
    this.$slides.each(function() {
      $(this).css('max-height', height);
    });
  }

  /**
  * Adds event listeners to basically everything within the element.
  * @function
  * @private
  */
  _events() {
    var _this = this;

    //***************************************
    //**Now using custom event - thanks to:**
    //**      Yohai Ararat of Toronto      **
    //***************************************
    if (this.$slides.length > 1) {

      if (this.options.swipe) {
        this.$slides.off('swipeleft.zf.orbit swiperight.zf.orbit')
        .on('swipeleft.zf.orbit', function(e){
          e.preventDefault();
          _this.changeSlide(true);
        }).on('swiperight.zf.orbit', function(e){
          e.preventDefault();
          _this.changeSlide(false);
        });
      }
      //***************************************

      if (this.options.autoPlay) {
        this.$slides.on('click.zf.orbit', function() {
          _this.$element.data('clickedOn', _this.$element.data('clickedOn') ? false : true);
          _this.timer[_this.$element.data('clickedOn') ? 'pause' : 'start']();
        });

        if (this.options.pauseOnHover) {
          this.$element.on('mouseenter.zf.orbit', function() {
            _this.timer.pause();
          }).on('mouseleave.zf.orbit', function() {
            if (!_this.$element.data('clickedOn')) {
              _this.timer.start();
            }
          });
        }
      }

      if (this.options.navButtons) {
        var $controls = this.$element.find(`.${this.options.nextClass}, .${this.options.prevClass}`);
        $controls.attr('tabindex', 0)
        //also need to handle enter/return and spacebar key presses
        .on('click.zf.orbit touchend.zf.orbit', function(){
	  e.preventDefault();
          _this.changeSlide($(this).hasClass(_this.options.nextClass));
        });
      }

      if (this.options.bullets) {
        this.$bullets.on('click.zf.orbit touchend.zf.orbit', function() {
          if (/is-active/g.test(this.className)) { return false; }//if this is active, kick out of function.
          var idx = $(this).data('slide'),
          ltr = idx > _this.$slides.filter('.is-active').data('slide'),
          $slide = _this.$slides.eq(idx);

          _this.changeSlide(ltr, $slide, idx);
        });
      }

      this.$wrapper.add(this.$bullets).on('keydown.zf.orbit', function(e) {
        // handle keyboard event with keyboard util
        Foundation.Keyboard.handleKey(e, 'Orbit', {
          next: function() {
            _this.changeSlide(true);
          },
          previous: function() {
            _this.changeSlide(false);
          },
          handled: function() { // if bullet is focused, make sure focus moves
            if ($(e.target).is(_this.$bullets)) {
              _this.$bullets.filter('.is-active').focus();
            }
          }
        });
      });
    }
  }

  /**
  * Changes the current slide to a new one.
  * @function
  * @param {Boolean} isLTR - flag if the slide should move left to right.
  * @param {jQuery} chosenSlide - the jQuery element of the slide to show next, if one is selected.
  * @param {Number} idx - the index of the new slide in its collection, if one chosen.
  * @fires Orbit#slidechange
  */
  changeSlide(isLTR, chosenSlide, idx) {
    var $curSlide = this.$slides.filter('.is-active').eq(0);

    if (/mui/g.test($curSlide[0].className)) { return false; } //if the slide is currently animating, kick out of the function

    var $firstSlide = this.$slides.first(),
    $lastSlide = this.$slides.last(),
    dirIn = isLTR ? 'Right' : 'Left',
    dirOut = isLTR ? 'Left' : 'Right',
    _this = this,
    $newSlide;

    if (!chosenSlide) { //most of the time, this will be auto played or clicked from the navButtons.
      $newSlide = isLTR ? //if wrapping enabled, check to see if there is a `next` or `prev` sibling, if not, select the first or last slide to fill in. if wrapping not enabled, attempt to select `next` or `prev`, if there's nothing there, the function will kick out on next step. CRAZY NESTED TERNARIES!!!!!
      (this.options.infiniteWrap ? $curSlide.next(`.${this.options.slideClass}`).length ? $curSlide.next(`.${this.options.slideClass}`) : $firstSlide : $curSlide.next(`.${this.options.slideClass}`))//pick next slide if moving left to right
      :
      (this.options.infiniteWrap ? $curSlide.prev(`.${this.options.slideClass}`).length ? $curSlide.prev(`.${this.options.slideClass}`) : $lastSlide : $curSlide.prev(`.${this.options.slideClass}`));//pick prev slide if moving right to left
    } else {
      $newSlide = chosenSlide;
    }

    if ($newSlide.length) {
      if (this.options.bullets) {
        idx = idx || this.$slides.index($newSlide); //grab index to update bullets
        this._updateBullets(idx);
      }

      if (this.options.useMUI) {
        Foundation.Motion.animateIn(
          $newSlide.addClass('is-active').css({'position': 'absolute', 'top': 0}),
          this.options[`animInFrom${dirIn}`],
          function(){
            $newSlide.css({'position': 'relative', 'display': 'block'})
            .attr('aria-live', 'polite');
        });

        Foundation.Motion.animateOut(
          $curSlide.removeClass('is-active'),
          this.options[`animOutTo${dirOut}`],
          function(){
            $curSlide.removeAttr('aria-live');
            if(_this.options.autoPlay && !_this.timer.isPaused){
              _this.timer.restart();
            }
            //do stuff?
          });
      } else {
        $curSlide.removeClass('is-active is-in').removeAttr('aria-live').hide();
        $newSlide.addClass('is-active is-in').attr('aria-live', 'polite').show();
        if (this.options.autoPlay && !this.timer.isPaused) {
          this.timer.restart();
        }
      }
    /**
    * Triggers when the slide has finished animating in.
    * @event Orbit#slidechange
    */
      this.$element.trigger('slidechange.zf.orbit', [$newSlide]);
    }
  }

  /**
  * Updates the active state of the bullets, if displayed.
  * @function
  * @private
  * @param {Number} idx - the index of the current slide.
  */
  _updateBullets(idx) {
    var $oldBullet = this.$element.find(`.${this.options.boxOfBullets}`)
    .find('.is-active').removeClass('is-active').blur(),
    span = $oldBullet.find('span:last').detach(),
    $newBullet = this.$bullets.eq(idx).addClass('is-active').append(span);
  }

  /**
  * Destroys the carousel and hides the element.
  * @function
  */
  destroy() {
    this.$element.off('.zf.orbit').find('*').off('.zf.orbit').end().hide();
    Foundation.unregisterPlugin(this);
  }
}

Orbit.defaults = {
  /**
  * Tells the JS to look for and loadBullets.
  * @option
  * @example true
  */
  bullets: true,
  /**
  * Tells the JS to apply event listeners to nav buttons
  * @option
  * @example true
  */
  navButtons: true,
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-in-right'
  */
  animInFromRight: 'slide-in-right',
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-out-right'
  */
  animOutToRight: 'slide-out-right',
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-in-left'
  *
  */
  animInFromLeft: 'slide-in-left',
  /**
  * motion-ui animation class to apply
  * @option
  * @example 'slide-out-left'
  */
  animOutToLeft: 'slide-out-left',
  /**
  * Allows Orbit to automatically animate on page load.
  * @option
  * @example true
  */
  autoPlay: true,
  /**
  * Amount of time, in ms, between slide transitions
  * @option
  * @example 5000
  */
  timerDelay: 5000,
  /**
  * Allows Orbit to infinitely loop through the slides
  * @option
  * @example true
  */
  infiniteWrap: true,
  /**
  * Allows the Orbit slides to bind to swipe events for mobile, requires an additional util library
  * @option
  * @example true
  */
  swipe: true,
  /**
  * Allows the timing function to pause animation on hover.
  * @option
  * @example true
  */
  pauseOnHover: true,
  /**
  * Allows Orbit to bind keyboard events to the slider, to animate frames with arrow keys
  * @option
  * @example true
  */
  accessible: true,
  /**
  * Class applied to the container of Orbit
  * @option
  * @example 'orbit-container'
  */
  containerClass: 'orbit-container',
  /**
  * Class applied to individual slides.
  * @option
  * @example 'orbit-slide'
  */
  slideClass: 'orbit-slide',
  /**
  * Class applied to the bullet container. You're welcome.
  * @option
  * @example 'orbit-bullets'
  */
  boxOfBullets: 'orbit-bullets',
  /**
  * Class applied to the `next` navigation button.
  * @option
  * @example 'orbit-next'
  */
  nextClass: 'orbit-next',
  /**
  * Class applied to the `previous` navigation button.
  * @option
  * @example 'orbit-previous'
  */
  prevClass: 'orbit-previous',
  /**
  * Boolean to flag the js to use motion ui classes or not. Default to true for backwards compatability.
  * @option
  * @example true
  */
  useMUI: true
};

// Window exports
Foundation.plugin(Orbit, 'Orbit');

}(jQuery);
<|endoftext|>


<commit_before>/**
 * @file Tag generation helper
 * @since 0.2.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfIsArray*/ // Return true if the parameter is an array
/*global _gpfIsLiteralObject*/ // Check if the parameter is a literal object
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*global _gpfStringEscapeFor*/ // Make the string content compatible with lang
/*global _gpfSyncReadSourceJSON*/ // Reads a source json file (only in source mode)
/*#endif*/

/**
 * Tag instance allocated by {@link gpf.typedef.tagFunc}
 *
 * @class gpf.typedef.Tag
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * String conversion
 *
 * @method gpf.typedef.Tag.prototype.toString
 * @return {String} HTML result
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * DOM insertion
 *
 * @method gpf.typedef.Tag.prototype.appendTo
 * @param {Object} node DOM parent to append the node to
 * @return {Object} Created DOM Node
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function child specification.  When string, a text node is created.
 *
 * @typedef gpf.typedef.tagChild
 * @property {String|gpf.typedef.Tag}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function children specification
 *
 * @typedef gpf.typedef.tagChildren
 * @property {gpf.typedef.tagChild|gpf.typedef.tagChild[]}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function, it accepts up to two parameters:
 * - When no parameter is passed, an empty node is created
 * - If the first parameter is a literal object (see {@link gpf.isLiteralObject}), its properties are used to
 *   define the node attributes
 * - Otherwise, the second (or first if not a literal object) is used to define children that will be appended to this
 *   tag
 *
 * @callback gpf.typedef.tagFunc
 *
 * @param {Object|gpf.typedef.tagChildren} [attributes=undefined] When a literal object is passed, it is interpreted
 * as an attribute dictionary. The attribute name may contain the xlink namespace prefix.
 * @param {gpf.typedef.tagChildren} [children=undefined] List of children
 * @return {gpf.typedef.Tag} Tag object
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

_gpfErrorDeclare("web/tag", {
    /**
     * ### Summary
     *
     * Missing node name
     *
     * ### Description
     *
     * A tag can't be created if the node name is missing
     * @since 0.2.1
     */
    missingNodeName: "Missing node name",

    /**
     * ### Summary
     *
     * Unknown namespace prefix
     *
     * ### Description
     *
     * A prefix has been used prior to be associated with a namespace
     * @since 0.2.2
     */
    unknownNamespacePrefix: "Unknown namespace prefix",

    /**
     * ### Summary
     *
     * Unable to use namespace in string
     *
     * ### Description
     *
     * A prefix associated to a namespace has been used and can't be converted to string
     * @since 0.2.2
     */
    unableToUseNamespaceInString: "Unable to use namespace in string"
});

/**
 * Mapping of attribute name aliases
 * @type {Object}
 * @since 0.2.1
 */
var _gpfWebTagAttributeAliases = _gpfSyncReadSourceJSON("web/attributes.json");

/**
 * Mapping of prefixes for namespaces
 * @type {Object}
 * @since 0.2.2
 */
var _gpfWebNamespacePrefix = _gpfSyncReadSourceJSON("web/namespaces.json");

/**
 * Retrieves namespace associated to the prefix or fail
 *
 * @param {String} prefix Namespace prefix
 * @return {String} Namespace URI
 * @throws {gpf.Error.UnknownNamespacePrefix}
 * @since 0.2.2
 */
function _gpfWebGetNamespace (prefix) {
    var namespace = _gpfWebNamespacePrefix[prefix];
    if (undefined === namespace) {
        gpf.Error.unknownNamespacePrefix();
    }
    return namespace;
}

/**
 * Resolves prefixed name to namespace and name
 *
 * @param {String} name Attribute or node name
 * @return {{namespace, name}|undefined} Namespace and name in a structure if prefixed, undefined otherwise
 * @since 0.2.2
 */
function _gpfWebGetNamespaceAndName (name) {
    var parts = name.split(":");
    if (parts.length === 2) {
        return {
            namespace: _gpfWebGetNamespace(parts[0]),
            name: parts[1]
        };
    }
}

/**
 * Fails if the name includes namespace prefix
 *
 * @param {String} name Attribute or node name
 * @throws {gpf.Error.UnableToUseNamespaceInString}
 * @since 0.2.2
 */
function _gpfWebCheckNamespaceSafe (name) {
    if (-1 !== name.indexOf(":")) {
        gpf.Error.unableToUseNamespaceInString();
    }
}

/**
 * Resolve attribute name
 *
 * @param {String} name Attribute name used in the tag function
 * @return {String} Attribute to set on the node ele,ment
 * @since 0.2.1
 */
function _gpfWebTagAttributeAlias (name) {
    return _gpfWebTagAttributeAliases[name] || name;
}

/**
 * Apply the callback to each array item,
 * process recursively if the array item is an array
 *
 * @param {Array} array array of items
 * @param {Function} callback Function to apply on each array item
 * @since 0.2.1
 */
function _gpfWebTagFlattenChildren (array, callback) {
    array.forEach(function (item) {
        if (_gpfIsArray(item)) {
            _gpfWebTagFlattenChildren(item, callback);
        } else {
            callback(item);
        }
    });
}

var _GpfWebTag = _gpfDefine({
    $class: "gpf.web.Tag",

    /**
     * Tag object
     *
     * @param {String} nodeName Node name
     * @param {Object} [attributes] Dictionary of attributes to set
     * @param {Array} [children] Children
     *
     * @constructor gpf.web.Tag
     * @private
     * @since 0.2.1
     */
    constructor: function (nodeName, attributes, children) {
        this._nodeName = nodeName;
        this._attributes = attributes || {};
        this._children = children;
    },

    /**
     * Node name
     * @since 0.2.1
     */
    _nodeName: "",

    /**
     * Node attributes
     * @since 0.2.1
     */
    _attributes: {},

    /**
     * Node children
     * @since 0.2.1
     */
    _children: [],

    //region toString implementation

    _getAttributesAsString: function () {
        return Object.keys(this._attributes).map(function (name) {
            _gpfWebCheckNamespaceSafe(name);
            return " " + _gpfWebTagAttributeAlias(name)
                + "=\"" + _gpfStringEscapeFor(this._attributes[name], "html") + "\"";
        }, this).join("");
    },

    _getChildrenAsString: function () {
        var result = [];
        _gpfWebTagFlattenChildren(this._children, function (child) {
            result.push(child.toString());
        });
        return result.join("");
    },

    _getClosingString: function () {
        if (this._children.length) {
            return ">" + this._getChildrenAsString() + "</" + this._nodeName + ">";
        }
        return "/>";
    },

    /**
     * Convert the current tag into HTML
     *
     * @return {String} HTML representation of the tag
     * @since 0.2.1
     */
    toString: function () {
        _gpfWebCheckNamespaceSafe(this._nodeName);
        return "<" + this._nodeName + this._getAttributesAsString() + this._getClosingString();
    },

    //endregion

    //region appendTo implementation

    _createElement: function (node) {
        var ownerDocument = node.ownerDocument,
            qualified = _gpfWebGetNamespaceAndName(this._nodeName);
        if (qualified) {
            return ownerDocument.createElementNS(qualified.namespace, qualified.name);
        }
        return ownerDocument.createElement(this._nodeName);
    },

    _setAttributesTo: function (node) {
        _gpfObjectForEach(this._attributes, function (value, name) {
            var qualified = _gpfWebGetNamespaceAndName(name);
            if (qualified) {
                node.setAttributeNS(qualified.namespace, _gpfWebTagAttributeAlias(qualified.name), value);
            } else {
                node.setAttribute(_gpfWebTagAttributeAlias(name), value);
            }
        });
    },

    _appendChildrenTo: function (node) {
        var ownerDocument = node.ownerDocument;
        _gpfWebTagFlattenChildren(this._children, function (child) {
            if (child instanceof _GpfWebTag) {
                child.appendTo(node);
            } else {
                node.appendChild(ownerDocument.createTextNode(child.toString()));
            }
        });
    },

    /**
     * Appends the tag to the provided node
     *
     * @param {Object} node Expected to be a DOM node
     * @return {Object} Created node
     * @since 0.2.1
     */
    appendTo: function (node) {
        var element = this._createElement(node);
        this._setAttributesTo(element);
        this._appendChildrenTo(element);
        return node.appendChild(element);
    }

    //endregion

});

/**
 * Create a tag generation function
 *
 * @param {String} nodeName tag name.
 * May include the namespace prefix svg for [SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG)
 * @return {gpf.typedef.tagFunc} The tag generation function
 * @gpf:closure
 * @since 0.2.1
 */
function _gpfWebTagCreateFunction (nodeName) {
    if (!nodeName) {
        gpf.Error.missingNodeName();
    }
    return function (firstParam) {
        var sliceFrom,
            attributes;
        if (_gpfIsLiteralObject(firstParam)) {
            attributes = firstParam;
            sliceFrom = 1;
        } else {
            sliceFrom = 0;
        }
        return new _GpfWebTag(nodeName, attributes, [].slice.call(arguments, sliceFrom));
    };
}

/**
 * @gpf:sameas _gpfWebTagCreateFunction
 * @since 0.2.1
 *
 * @example
Tree building to string

 * var div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 * tree = div({className: "test1"}, "Hello ", span({className: "test2"}, "World!"));
 * // tree.toString() gives
Hello World!

 *
 * @example
Tree building to DOM

 * var mockNode = mockDocument.createElement("any"),
 *     div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 *     tree = div({className: "test"}, "Hello ", span("World!")),
 *     result = tree.appendTo(mockNode);
 *
 * @example
SVG building

 * var mockNode = mockDocument.createElement("any"),
 *     svgImage = gpf.web.createTagFunction("svg:image"),
 *     tree = svgImage({x: 0, y: 0, "xlink:href": "test.png"}),
 *     result = tree.appendTo(mockNode);
 *
 */
gpf.web.createTagFunction = _gpfWebTagCreateFunction;
<commit_msg>!yoda style (#280)<commit_after>/**
 * @file Tag generation helper
 * @since 0.2.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfIsArray*/ // Return true if the parameter is an array
/*global _gpfIsLiteralObject*/ // Check if the parameter is a literal object
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*global _gpfStringEscapeFor*/ // Make the string content compatible with lang
/*global _gpfSyncReadSourceJSON*/ // Reads a source json file (only in source mode)
/*#endif*/

/**
 * Tag instance allocated by {@link gpf.typedef.tagFunc}
 *
 * @class gpf.typedef.Tag
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * String conversion
 *
 * @method gpf.typedef.Tag.prototype.toString
 * @return {String} HTML result
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * DOM insertion
 *
 * @method gpf.typedef.Tag.prototype.appendTo
 * @param {Object} node DOM parent to append the node to
 * @return {Object} Created DOM Node
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function child specification.  When string, a text node is created.
 *
 * @typedef gpf.typedef.tagChild
 * @property {String|gpf.typedef.Tag}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function children specification
 *
 * @typedef gpf.typedef.tagChildren
 * @property {gpf.typedef.tagChild|gpf.typedef.tagChild[]}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function, it accepts up to two parameters:
 * - When no parameter is passed, an empty node is created
 * - If the first parameter is a literal object (see {@link gpf.isLiteralObject}), its properties are used to
 *   define the node attributes
 * - Otherwise, the second (or first if not a literal object) is used to define children that will be appended to this
 *   tag
 *
 * @callback gpf.typedef.tagFunc
 *
 * @param {Object|gpf.typedef.tagChildren} [attributes=undefined] When a literal object is passed, it is interpreted
 * as an attribute dictionary. The attribute name may contain the xlink namespace prefix.
 * @param {gpf.typedef.tagChildren} [children=undefined] List of children
 * @return {gpf.typedef.Tag} Tag object
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

_gpfErrorDeclare("web/tag", {
    /**
     * ### Summary
     *
     * Missing node name
     *
     * ### Description
     *
     * A tag can't be created if the node name is missing
     * @since 0.2.1
     */
    missingNodeName: "Missing node name",

    /**
     * ### Summary
     *
     * Unknown namespace prefix
     *
     * ### Description
     *
     * A prefix has been used prior to be associated with a namespace
     * @since 0.2.2
     */
    unknownNamespacePrefix: "Unknown namespace prefix",

    /**
     * ### Summary
     *
     * Unable to use namespace in string
     *
     * ### Description
     *
     * A prefix associated to a namespace has been used and can't be converted to string
     * @since 0.2.2
     */
    unableToUseNamespaceInString: "Unable to use namespace in string"
});

/**
 * Mapping of attribute name aliases
 * @type {Object}
 * @since 0.2.1
 */
var _gpfWebTagAttributeAliases = _gpfSyncReadSourceJSON("web/attributes.json");

/**
 * Mapping of prefixes for namespaces
 * @type {Object}
 * @since 0.2.2
 */
var _gpfWebNamespacePrefix = _gpfSyncReadSourceJSON("web/namespaces.json");

/**
 * Retrieves namespace associated to the prefix or fail
 *
 * @param {String} prefix Namespace prefix
 * @return {String} Namespace URI
 * @throws {gpf.Error.UnknownNamespacePrefix}
 * @since 0.2.2
 */
function _gpfWebGetNamespace (prefix) {
    var namespace = _gpfWebNamespacePrefix[prefix];
    if (undefined === namespace) {
        gpf.Error.unknownNamespacePrefix();
    }
    return namespace;
}

/**
 * Resolves prefixed name to namespace and name
 *
 * @param {String} name Attribute or node name
 * @return {{namespace, name}|undefined} Namespace and name in a structure if prefixed, undefined otherwise
 * @since 0.2.2
 */
function _gpfWebGetNamespaceAndName (name) {
    var parts = name.split(":");
    if (parts.length === 2) {
        return {
            namespace: _gpfWebGetNamespace(parts[0]),
            name: parts[1]
        };
    }
}

/**
 * Fails if the name includes namespace prefix
 *
 * @param {String} name Attribute or node name
 * @throws {gpf.Error.UnableToUseNamespaceInString}
 * @since 0.2.2
 */
function _gpfWebCheckNamespaceSafe (name) {
    if (name.indexOf(":") !== -1) {
        gpf.Error.unableToUseNamespaceInString();
    }
}

/**
 * Resolve attribute name
 *
 * @param {String} name Attribute name used in the tag function
 * @return {String} Attribute to set on the node ele,ment
 * @since 0.2.1
 */
function _gpfWebTagAttributeAlias (name) {
    return _gpfWebTagAttributeAliases[name] || name;
}

/**
 * Apply the callback to each array item,
 * process recursively if the array item is an array
 *
 * @param {Array} array array of items
 * @param {Function} callback Function to apply on each array item
 * @since 0.2.1
 */
function _gpfWebTagFlattenChildren (array, callback) {
    array.forEach(function (item) {
        if (_gpfIsArray(item)) {
            _gpfWebTagFlattenChildren(item, callback);
        } else {
            callback(item);
        }
    });
}

var _GpfWebTag = _gpfDefine({
    $class: "gpf.web.Tag",

    /**
     * Tag object
     *
     * @param {String} nodeName Node name
     * @param {Object} [attributes] Dictionary of attributes to set
     * @param {Array} [children] Children
     *
     * @constructor gpf.web.Tag
     * @private
     * @since 0.2.1
     */
    constructor: function (nodeName, attributes, children) {
        this._nodeName = nodeName;
        this._attributes = attributes || {};
        this._children = children;
    },

    /**
     * Node name
     * @since 0.2.1
     */
    _nodeName: "",

    /**
     * Node attributes
     * @since 0.2.1
     */
    _attributes: {},

    /**
     * Node children
     * @since 0.2.1
     */
    _children: [],

    //region toString implementation

    _getAttributesAsString: function () {
        return Object.keys(this._attributes).map(function (name) {
            _gpfWebCheckNamespaceSafe(name);
            return " " + _gpfWebTagAttributeAlias(name)
                + "=\"" + _gpfStringEscapeFor(this._attributes[name], "html") + "\"";
        }, this).join("");
    },

    _getChildrenAsString: function () {
        var result = [];
        _gpfWebTagFlattenChildren(this._children, function (child) {
            result.push(child.toString());
        });
        return result.join("");
    },

    _getClosingString: function () {
        if (this._children.length) {
            return ">" + this._getChildrenAsString() + "</" + this._nodeName + ">";
        }
        return "/>";
    },

    /**
     * Convert the current tag into HTML
     *
     * @return {String} HTML representation of the tag
     * @since 0.2.1
     */
    toString: function () {
        _gpfWebCheckNamespaceSafe(this._nodeName);
        return "<" + this._nodeName + this._getAttributesAsString() + this._getClosingString();
    },

    //endregion

    //region appendTo implementation

    _createElement: function (node) {
        var ownerDocument = node.ownerDocument,
            qualified = _gpfWebGetNamespaceAndName(this._nodeName);
        if (qualified) {
            return ownerDocument.createElementNS(qualified.namespace, qualified.name);
        }
        return ownerDocument.createElement(this._nodeName);
    },

    _setAttributesTo: function (node) {
        _gpfObjectForEach(this._attributes, function (value, name) {
            var qualified = _gpfWebGetNamespaceAndName(name);
            if (qualified) {
                node.setAttributeNS(qualified.namespace, _gpfWebTagAttributeAlias(qualified.name), value);
            } else {
                node.setAttribute(_gpfWebTagAttributeAlias(name), value);
            }
        });
    },

    _appendChildrenTo: function (node) {
        var ownerDocument = node.ownerDocument;
        _gpfWebTagFlattenChildren(this._children, function (child) {
            if (child instanceof _GpfWebTag) {
                child.appendTo(node);
            } else {
                node.appendChild(ownerDocument.createTextNode(child.toString()));
            }
        });
    },

    /**
     * Appends the tag to the provided node
     *
     * @param {Object} node Expected to be a DOM node
     * @return {Object} Created node
     * @since 0.2.1
     */
    appendTo: function (node) {
        var element = this._createElement(node);
        this._setAttributesTo(element);
        this._appendChildrenTo(element);
        return node.appendChild(element);
    }

    //endregion

});

/**
 * Create a tag generation function
 *
 * @param {String} nodeName tag name.
 * May include the namespace prefix svg for [SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG)
 * @return {gpf.typedef.tagFunc} The tag generation function
 * @gpf:closure
 * @since 0.2.1
 */
function _gpfWebTagCreateFunction (nodeName) {
    if (!nodeName) {
        gpf.Error.missingNodeName();
    }
    return function (firstParam) {
        var sliceFrom,
            attributes;
        if (_gpfIsLiteralObject(firstParam)) {
            attributes = firstParam;
            sliceFrom = 1;
        } else {
            sliceFrom = 0;
        }
        return new _GpfWebTag(nodeName, attributes, [].slice.call(arguments, sliceFrom));
    };
}

/**
 * @gpf:sameas _gpfWebTagCreateFunction
 * @since 0.2.1
 *
 * @example
Tree building to string

 * var div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 * tree = div({className: "test1"}, "Hello ", span({className: "test2"}, "World!"));
 * // tree.toString() gives
Hello World!

 *
 * @example
Tree building to DOM

 * var mockNode = mockDocument.createElement("any"),
 *     div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 *     tree = div({className: "test"}, "Hello ", span("World!")),
 *     result = tree.appendTo(mockNode);
 *
 * @example
SVG building

 * var mockNode = mockDocument.createElement("any"),
 *     svgImage = gpf.web.createTagFunction("svg:image"),
 *     tree = svgImage({x: 0, y: 0, "xlink:href": "test.png"}),
 *     result = tree.appendTo(mockNode);
 *
 */
gpf.web.createTagFunction = _gpfWebTagCreateFunction;
<|endoftext|>