<commit_before>/**
 * @requires PF.Heap
 */

/**
 * A* path-finder.
 * Note: The constructor does NOT receive any arguments for instantiation.
 *     All the parameters should be passed in in the `init` method.
 *     Therefore, you only need to instantiate this class once, and call
 *     the `init` method each time you start a new path-finding.
 * @constructor
 * @extends PF.BaseFinder
 * @param {function(number, number): number} [heuristic] - Heuristic function
 *     being used to estimate the distance(defaults to manhattan).
 * @example
 * var finder, path;
 * finder = PF.AStarFinder();
 * finder.init(...);
 * path = finder.findPath();
 *
 * // Available heuristics:
 * //     PF.AStarFinder.manhattan
 * //     PF.AStarFinder.euclidean
 * //     PF.AStarFinder.chebyshev
 * //
 * // Sample Custom heuristic:
 * function(dx, dy) {
 *     return Math.min(dx, dy);
 * }
 *
 */
PF.AStarFinder = function(heuristic) {
    PF.BaseFinder.call(this);

    this.heuristic = heuristic || PF.AStarFinder.manhattan;
};

/**
 * Extends the BaseFinder
 */
PF.AStarFinder.prototype = new PF.BaseFinder();

/**
 * The constructor of the instance.
 */
PF.AStarFinder.prototype.constructor = PF.AStarFinder;

/**
 * Initiate the path-finder by providing the coordinates and the grid.
 * @param {number} startX - The x coordinate of the start position.
 * @param {number} startY - The y coordinate of the start position.
 * @param {number} endX - The x coordinate of the end position.
 * @param {number} endY - The y coordinate of the end position.
 */
PF.AStarFinder.prototype.init = function(startX, startY, endX, endY, grid) {
    PF.BaseFinder.prototype.init.call(this, startX, startY, endX, endY, grid);

    var grid = this.grid;

    this.openList = new PF.Heap(function(a, b) {
        return grid.getAttributeAt(a[0], a[1], 'f') <
               grid.getAttributeAt(b[0], b[1], 'f');
    });
};

/**
 * Find and return the the path.
 * NOTE: This method is intended to be overriden by sub-classes.
 * @return {Array.<[number, number]>} The path, including both start and
 *     end positions.
 */
PF.AStarFinder.prototype.findPath = function() {
    var x, y,
        nx, ny,
        sx = this.startX,
        sy = this.startY,
        ex = this.endX,
        ey = this.endY,
        openList = this.openList,
        grid = this.grid,
        node;

        xOffsets = [-1, 0, 0, 1],
        yOffsets = [0, -1, 1, 0];

    // set the `g` and `f` value of the start node to be 0
    node = grid.getNodeAt(sx, sy);
    node.set('g', 0);
    node.set('f', 0);

    // push the start node into the open list
    openList.push([sx, sy]);
    node.set('opened', true);

    // while the open list is not empty
    while (!openList.isEmpty()) {
        // pop the position of node which has the minimum `f` value.
        pos = openList.pop();
        x = pos[0];
        y = pos[1];
        grid.setAttributeAt(x, y, 'closed', true);

        // if reached the end position, construct the path and return it
        if (x == ex && y == ey) {
            return this._constructPath();
        }

        // enumerate the ajacent positions
        // TODO: make this procedure independent and switchable
        for (var i = 0; i < xOffsets.length; ++i) {
            nx = x + xOffsets[i];
            ny = y + yOffsets[i];

            if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
                this._inspectNodeAt(nx, ny, x, y);
            }
        }
    }

    return [];
};

/**
 * Construct the path according to the nodes' parents.
 * @private
 * @return {Array.<Array.<number>>} The path, including
 *     both start and end positions.
 */
PF.AStarFinder.prototype._constructPath = function() {
    var sx = this.startX, sy = this.startY,
        x, y,
        grid = this.grid,
        path = [[this.endX, this.endY]];

    for (;;) {
        x = path[0][0];
        y = path[0][1];
        if (x == sx && y == sy) {
            return path;
        }
        path.unshift(grid.getAttributeAt(x, y, 'parent'));
    }

    // it should never reach here.
    return [];
};

/**
 * Push the position into the open list if this position is not in the list.
 * Otherwise, if the position can be accessed with a lower cost from the given
 * parent position, then update its parent and cost
 * @private
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 */
PF.AStarFinder.prototype._inspectNodeAt = function(x, y, px, py) {
    var grid = this.grid,
        openList = this.openList,
        node = grid.getNodeAt(x, y);

    if (node.get('closed')) {
        return;
    }

    if (node.get('opened')) {
        //
    } else {
        node.set('opened', true);
        openList.push([x, y]);
    }
    this._tryUpdate(x, y, px, py);
};

/**
 * Try to update the position's info with the given parent.
 * If this position can be accessed from the given parent with lower
 * `g` cost, then this position's parent, `g` and `f` values will be updated.
 * @private
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 * @return {boolean} Whether this position's info has been updated.
 */
PF.AStarFinder.prototype._tryUpdate = function(x, y, px, py) {
    var grid = this.grid,
        pNode = grid.getNodeAt(px, py), // parent node
        ng = pNode.g + 1; // next `g` value
        node = grid.getNodeAt(x, y);

    if (node.get('g') === undefined || ng < node.get('g')) {
        node.set('parent', [px, py]);
        node.set('g', ng);
        node.set('h', this._calculateH(x, y));
        node.set('f', node.g + node.h);
        return true;
    }
    return false;
};

/**
 * Calculate the `h` value of a given position.
 * @private
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @return {number}
 */
PF.AStarFinder.prototype._calculateH = function(x, y) {
    var dx = Math.abs(x - this.endX),
        dy = Math.abs(y - this.endY);
    return this.heuristic(dx, dy);
};

/**
 * Manhattan distance.
 * @description dx + dy
 */
PF.AStarFinder.manhattan = function(dx, dy) {
    return dx + dy;
};

/**
 * Euclidean distance.
 * @description sqrt(dx * dx, dy * dy)
 */
PF.AStarFinder.euclidean = function(dx, dy) {
    return Math.sqrt(dx * dx + dy * dy);
};

/**
 * Chebyshev distance.
 * @description max(dx, dy)
 */
PF.AStarFinder.chebyshev = function(dx, dy) {
    return Math.max(dx, dy);
};
<commit_msg>mod: simplified invocation for finding the path<commit_after>/**
 * @requires PF.Heap
 */

/**
 * A* path-finder.
 * @constructor
 * @extends PF.BaseFinder
 * @param {function(number, number): number} [heuristic] - Heuristic function
 *     being used to estimate the distance(defaults to manhattan).
 * @example
 * var finder, path;
 * finder = PF.AStarFinder();
 * path = finder.findPath(...);
 *
 * // Available heuristics:
 * //     PF.AStarFinder.manhattan
 * //     PF.AStarFinder.euclidean
 * //     PF.AStarFinder.chebyshev
 * //
 * // Sample Custom heuristic:
 * function(dx, dy) {
 *     return Math.min(dx, dy);
 * }
 *
 */
PF.AStarFinder = function(heuristic) {
    PF.BaseFinder.call(this);

    this.heuristic = heuristic || PF.AStarFinder.manhattan;
};

/**
 * Extends the BaseFinder
 */
PF.AStarFinder.prototype = new PF.BaseFinder();

/**
 * The constructor of the instance.
 */
PF.AStarFinder.prototype.constructor = PF.AStarFinder;

/**
 * Initiate the path-finder by providing the coordinates and the grid.
 * @param {number} startX - The x coordinate of the start position.
 * @param {number} startY - The y coordinate of the start position.
 * @param {number} endX - The x coordinate of the end position.
 * @param {number} endY - The y coordinate of the end position.
 * @private
 */
PF.AStarFinder.prototype.init = function(startX, startY, endX, endY, grid) {
    PF.BaseFinder.prototype.init.call(this, startX, startY, endX, endY, grid);

    var grid = this.grid;

    this.openList = new PF.Heap(function(a, b) {
        return grid.getAttributeAt(a[0], a[1], 'f') <
               grid.getAttributeAt(b[0], b[1], 'f');
    });
};

/**
 * Find and return the the path.
 * NOTE: This method is intended to be overriden by sub-classes.
 * @param {number} startX - The x coordinate of the start position.
 * @param {number} startY - The y coordinate of the start position.
 * @param {number} endX - The x coordinate of the end position.
 * @param {number} endY - The y coordinate of the end position.
 * @return {Array.<[number, number]>} The path, including both start and
 *     end positions.
 */
PF.AStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) {
    this.init(startX, startY, endX, endY, grid);

    var x, y,
        nx, ny,
        sx = this.startX,
        sy = this.startY,
        ex = this.endX,
        ey = this.endY,
        openList = this.openList,
        grid = this.grid,
        node;

        xOffsets = [-1, 0, 0, 1],
        yOffsets = [0, -1, 1, 0];

    // set the `g` and `f` value of the start node to be 0
    node = grid.getNodeAt(sx, sy);
    node.set('g', 0);
    node.set('f', 0);

    // push the start node into the open list
    openList.push([sx, sy]);
    node.set('opened', true);

    // while the open list is not empty
    while (!openList.isEmpty()) {
        // pop the position of node which has the minimum `f` value.
        pos = openList.pop();
        x = pos[0];
        y = pos[1];
        grid.setAttributeAt(x, y, 'closed', true);

        // if reached the end position, construct the path and return it
        if (x == ex && y == ey) {
            return this._constructPath();
        }

        // enumerate the ajacent positions
        // TODO: make this procedure independent and switchable
        for (var i = 0; i < xOffsets.length; ++i) {
            nx = x + xOffsets[i];
            ny = y + yOffsets[i];

            if (grid.isInside(nx, ny) && grid.isWalkableAt(nx, ny)) {
                this._inspectNodeAt(nx, ny, x, y);
            }
        }
    }

    return [];
};

/**
 * Construct the path according to the nodes' parents.
 * @private
 * @return {Array.<Array.<number>>} The path, including
 *     both start and end positions.
 */
PF.AStarFinder.prototype._constructPath = function() {
    var sx = this.startX, sy = this.startY,
        x, y,
        grid = this.grid,
        path = [[this.endX, this.endY]];

    for (;;) {
        x = path[0][0];
        y = path[0][1];
        if (x == sx && y == sy) {
            return path;
        }
        path.unshift(grid.getAttributeAt(x, y, 'parent'));
    }

    // it should never reach here.
    return [];
};

/**
 * Push the position into the open list if this position is not in the list.
 * Otherwise, if the position can be accessed with a lower cost from the given
 * parent position, then update its parent and cost
 * @private
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 */
PF.AStarFinder.prototype._inspectNodeAt = function(x, y, px, py) {
    var grid = this.grid,
        openList = this.openList,
        node = grid.getNodeAt(x, y);

    if (node.get('closed')) {
        return;
    }

    if (node.get('opened')) {
        //
    } else {
        node.set('opened', true);
        openList.push([x, y]);
    }
    this._tryUpdate(x, y, px, py);
};

/**
 * Try to update the position's info with the given parent.
 * If this position can be accessed from the given parent with lower
 * `g` cost, then this position's parent, `g` and `f` values will be updated.
 * @private
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @param {number} px - The x coordinate of the parent position.
 * @param {number} py - The y coordinate of the parent position.
 * @return {boolean} Whether this position's info has been updated.
 */
PF.AStarFinder.prototype._tryUpdate = function(x, y, px, py) {
    var grid = this.grid,
        pNode = grid.getNodeAt(px, py), // parent node
        ng = pNode.g + 1; // next `g` value
        node = grid.getNodeAt(x, y);

    if (node.get('g') === undefined || ng < node.get('g')) {
        node.set('parent', [px, py]);
        node.set('g', ng);
        node.set('h', this._calculateH(x, y));
        node.set('f', node.g + node.h);
        return true;
    }
    return false;
};

/**
 * Calculate the `h` value of a given position.
 * @private
 * @param {number} x - The x coordinate of the position.
 * @param {number} y - The y coordinate of the position.
 * @return {number}
 */
PF.AStarFinder.prototype._calculateH = function(x, y) {
    var dx = Math.abs(x - this.endX),
        dy = Math.abs(y - this.endY);
    return this.heuristic(dx, dy);
};

/**
 * Manhattan distance.
 * @description dx + dy
 */
PF.AStarFinder.manhattan = function(dx, dy) {
    return dx + dy;
};

/**
 * Euclidean distance.
 * @description sqrt(dx * dx, dy * dy)
 */
PF.AStarFinder.euclidean = function(dx, dy) {
    return Math.sqrt(dx * dx + dy * dy);
};

/**
 * Chebyshev distance.
 * @description max(dx, dy)
 */
PF.AStarFinder.chebyshev = function(dx, dy) {
    return Math.max(dx, dy);
};
<|endoftext|>


<commit_before>/**
 * Common Collection operations, use this if you have an array
 * of objects that you want to perform many operations on
**/
angular.module("ngCollection", [])
	.factory('Collection', function() {

        // Constructure, expects an array of objects
        function Collection(data_array) {
            if (Object.prototype.toString.call(data_array) !== '[object Array]') data_array = [];
            this.raw_data = data_array;
        }

        // Return the total number of elements in the collection
        Collection.prototype.count = function() {
            return this.raw_data.length;
        }

        // Return whether or not the collection is empty
        Collection.prototype.empty = function() {
            return this.count() <= 0;
        }

        // Return the first element in the collection
        Collection.prototype.first = function() {
            return this.empty() ? null : this.raw_data[0];
        }

        // Return the raw array of items
        Collection.prototype.items = function() {
            return this.raw_data;
        }

        // Return the last element in the collection
        Collection.prototype.last = function() {
            return this.empty() ? null : this.raw_data[this.raw_data.length - 1];
        }

        // Push a new element to the collection
        Collection.prototype.push = function(new_item) {
            this.raw_data.push(new_item);
            return this;
        }

        // Return the overall Collection average of a given property.
        // Optionally ignore elements where the group property value is
        // equal to zero.
        Collection.prototype.averageBy = function(avg_property, exclude_zeros) {

            if(exclude_zeros) {

                return this.subset(function(v, i, a) {
                    return a[i][avg_property] > 0;
                }).averageBy(avg_property);

            }

            return this.empty() ? 0 : (this.sumBy(avg_property) / this.count());
        }

        // Return an array where the indices correspond to the unique values of
        // the given group property and the values are the number of times that
        // value was found in the original Collection. Optionally ignore elements
        // where the group property value is empty.
        Collection.prototype.countBy = function(group_property, exclude_empty_values) {
            var counts = {};
            var i;
            if (this.empty()) return counts;
            for (i in this.raw_data) {
				var key = this.raw_data[i][group_property];
				if (key === true || key === false) key = key ? 1 : 0;
                if (exclude_empty_values && key == '') continue;
                if (!counts.hasOwnProperty(key)) {
                    counts[key] = 0;
                }
                counts[key]++;
            }
            return counts;
        }

        // Return an array of unique values for the given property. Optionally
        // ignore elements where the fetch property value is empty.
        Collection.prototype.fetchUniqueValues = function(fetch_property, exclude_empty_values) {
            return $.map(this.countBy(fetch_property, exclude_empty_values), function (v,i) { return i; });
        }

        // Reduce the current collection to only those items that satisfy the
        // provided closure; function(value, index, array)
        // See .subset to get a subset of items without modifying original instance
        Collection.prototype.filter = function(closure) {
            this.raw_data = this.raw_data.filter(closure);
            return this;
        }

        // Return an array of Collections where the indices correspond to the unique
        // values of the given group property. Optionally ignore elements where the
        // group property value is empty.
        Collection.prototype.groupBy = function(group_property, exclude_empty_values) {
            var sorted = {};
            var i;
            if (this.empty()) return sorted;
            for (i in this.raw_data) {
				var key = this.raw_data[i][group_property];
				if (key === true || key === false) key = key ? 1 : 0;

                if (exclude_empty_values && this.raw_data[i][group_property] == '') continue;
                if (!sorted.hasOwnProperty(key)) {
                    sorted[key] = new Collection();
                }
                sorted[key].push(this.raw_data[i]);
            }
            return sorted;
        }

        // Find the maximum value of the given property name in the Collection
        Collection.prototype.max = function(property) {
            return Math.max.apply(Math, this.fetchUniqueValues(property));
        }

		// Find the minimum value of the given property name in the Collection
        Collection.prototype.min = function(property) {
            return Math.min.apply(Math, this.fetchUniqueValues(property));
        }

        /**
         * Given a property name and a val (or array of vals), pull the item(s)
         * that match the given val(s) for that property from the array.
         * Note: this will remove any matches from the collection
         *
         * TODO: Create a fetchByVal that does something similar but doesn't
         *       modify the collection
         *
         * Returns:
         *   if a single, non-array value is requested and found, that item (object)
         *   if a single, non-array value is requested and not found, null
         *   if an array of values is requested, a Collection is always returned (even if empty)
         **/
        Collection.prototype.pullByVal = function(property, vals) {
            var multiples = vals instanceof Array;
            var result = (multiples ? new Collection() : null);

            if (multiples) {
                var valObj = {}; // make val list an object for quicker searching
                vals.forEach(function(v) { valObj[v] = 1; });
            }

            this.raw_data.forEach(function(v, i, a) {
                if (multiples) {
                    if (valObj.hasOwnProperty(v[property])) {
                        result.push(v);
                        a.splice(i,1);
                    }
                }
                else if (v[property] === vals) {
                    result = v;
                    a.splice(i,1);
                }
            });

            return result;
        }

        // Sort the current collection instance by the given property name
        // Note: this currently always sorts ascending and is case INsensitive
		// TODO: sort descending . . . and case sensitive sorting?
        Collection.prototype.sortBy = function(sort_property) {
            this.raw_data.sort(function(a,b) {
                var A = a[sort_property];
                var B = b[sort_property];

                // Make strings lowercase (case insensitive)
                if(isNaN(parseFloat(A)) || !isFinite(A)) {
                    A = A.toLowerCase();
                }

                if(isNaN(parseFloat(B)) || !isFinite(B)) {
                    B = B.toLowerCase();
                }

                if (A < B) {
                    return -1
                } else if (A > B) {
                    return 1;
                } else {
                    return 0;
                }
            });

            return this;
        }

        // Find a subset of items based on a given closure; function(value, index, array)
        // subset of items returned as a new Collection instance.
        // Differs from .filter in that it does not modify current instance.
        Collection.prototype.subset = function(closure) {
            return new Collection(this.raw_data.filter(closure));
        }

        // Calculate the sum of all values of the given property name
        // in the collection.
        Collection.prototype.sumBy = function(sum_property) {
            var sum = 0;
            if (this.empty()) return sum;
            var i;
            for (i in this.raw_data) {
                sum = sum + parseFloat(this.raw_data[i][sum_property]);
            }
            return sum;
        }

        // Run provided closure against each element in the collection
        Collection.prototype.transform = function(closure) {
            this.raw_data.forEach(closure);
            return this;
        }

        return Collection;
    });<commit_msg>Quick fix for sortBy and add ability to sort descending.<commit_after>/**
 * Common Collection operations, use this if you have an array
 * of objects that you want to perform many operations on
**/
angular.module("ngCollection", [])
	.factory('Collection', function() {

        // Constructure, expects an array of objects
        function Collection(data_array) {
            if (Object.prototype.toString.call(data_array) !== '[object Array]') data_array = [];
            this.raw_data = data_array;
        }

        // Return the total number of elements in the collection
        Collection.prototype.count = function() {
            return this.raw_data.length;
        }

        // Return whether or not the collection is empty
        Collection.prototype.empty = function() {
            return this.count() <= 0;
        }

        // Return the first element in the collection
        Collection.prototype.first = function() {
            return this.empty() ? null : this.raw_data[0];
        }

        // Return the raw array of items
        Collection.prototype.items = function() {
            return this.raw_data;
        }

        // Return the last element in the collection
        Collection.prototype.last = function() {
            return this.empty() ? null : this.raw_data[this.raw_data.length - 1];
        }

        // Push a new element to the collection
        Collection.prototype.push = function(new_item) {
            this.raw_data.push(new_item);
            return this;
        }

        // Return the overall Collection average of a given property.
        // Optionally ignore elements where the group property value is
        // equal to zero.
        Collection.prototype.averageBy = function(avg_property, exclude_zeros) {

            if(exclude_zeros) {

                return this.subset(function(v, i, a) {
                    return a[i][avg_property] > 0;
                }).averageBy(avg_property);

            }

            return this.empty() ? 0 : (this.sumBy(avg_property) / this.count());
        }

        // Return an array where the indices correspond to the unique values of
        // the given group property and the values are the number of times that
        // value was found in the original Collection. Optionally ignore elements
        // where the group property value is empty.
        Collection.prototype.countBy = function(group_property, exclude_empty_values) {
            var counts = {};
            var i;
            if (this.empty()) return counts;
            for (i in this.raw_data) {
				var key = this.raw_data[i][group_property];
				if (key === true || key === false) key = key ? 1 : 0;
                if (exclude_empty_values && key == '') continue;
                if (!counts.hasOwnProperty(key)) {
                    counts[key] = 0;
                }
                counts[key]++;
            }
            return counts;
        }

        // Return an array of unique values for the given property. Optionally
        // ignore elements where the fetch property value is empty.
        Collection.prototype.fetchUniqueValues = function(fetch_property, exclude_empty_values) {
            return $.map(this.countBy(fetch_property, exclude_empty_values), function (v,i) { return i; });
        }

        // Reduce the current collection to only those items that satisfy the
        // provided closure; function(value, index, array)
        // See .subset to get a subset of items without modifying original instance
        Collection.prototype.filter = function(closure) {
            this.raw_data = this.raw_data.filter(closure);
            return this;
        }

        // Return an array of Collections where the indices correspond to the unique
        // values of the given group property. Optionally ignore elements where the
        // group property value is empty.
        Collection.prototype.groupBy = function(group_property, exclude_empty_values) {
            var sorted = {};
            var i;
            if (this.empty()) return sorted;
            for (i in this.raw_data) {
				var key = this.raw_data[i][group_property];
				if (key === true || key === false) key = key ? 1 : 0;

                if (exclude_empty_values && this.raw_data[i][group_property] == '') continue;
                if (!sorted.hasOwnProperty(key)) {
                    sorted[key] = new Collection();
                }
                sorted[key].push(this.raw_data[i]);
            }
            return sorted;
        }

        // Find the maximum value of the given property name in the Collection
        Collection.prototype.max = function(property) {
            return Math.max.apply(Math, this.fetchUniqueValues(property));
        }

		// Find the minimum value of the given property name in the Collection
        Collection.prototype.min = function(property) {
            return Math.min.apply(Math, this.fetchUniqueValues(property));
        }

        /**
         * Given a property name and a val (or array of vals), pull the item(s)
         * that match the given val(s) for that property from the array.
         * Note: this will remove any matches from the collection
         *
         * TODO: Create a fetchByVal that does something similar but doesn't
         *       modify the collection
         *
         * Returns:
         *   if a single, non-array value is requested and found, that item (object)
         *   if a single, non-array value is requested and not found, null
         *   if an array of values is requested, a Collection is always returned (even if empty)
         **/
        Collection.prototype.pullByVal = function(property, vals) {
            var multiples = vals instanceof Array;
            var result = (multiples ? new Collection() : null);

            if (multiples) {
                var valObj = {}; // make val list an object for quicker searching
                vals.forEach(function(v) { valObj[v] = 1; });
            }

            this.raw_data.forEach(function(v, i, a) {
                if (multiples) {
                    if (valObj.hasOwnProperty(v[property])) {
                        result.push(v);
                        a.splice(i,1);
                    }
                }
                else if (v[property] === vals) {
                    result = v;
                    a.splice(i,1);
                }
            });

            return result;
        }

        // Sort the current collection instance ASCENDING by the given property name
        // Note: this currently always sorts case INsensitive
        Collection.prototype.sortBy = function(sort_property) {
            this.raw_data.sort(function(a,b) {
                var A = a[sort_property];
                var B = b[sort_property];

                // Make strings lowercase (case insensitive)
                if(A && (isNaN(parseFloat(A)) || !isFinite(A))) {
                    A = A.toLowerCase();
                }

                if(B && (isNaN(parseFloat(B)) || !isFinite(B))) {
                    B = B.toLowerCase();
                }

                if (A < B) {
                    return -1
                } else if (A > B) {
                    return 1;
                } else {
                    return 0;
                }
            });

            return this;
        }

        // Sort the current collection instance DESCENDING by the given property name
        // Note: this currently always sorts case INsensitive
        Collection.prototype.rSortBy = function(sort_property) {
            this.raw_data.sort(function(a,b) {
                var A = a[sort_property];
                var B = b[sort_property];

                // Make strings lowercase (case insensitive)
                if(A instanceof Date) {
                	A = A.getTime();
                }
				else if(A && (isNaN(parseFloat(A)) || !isFinite(A))) {
                    A = A.toLowerCase();
                }

				if(B instanceof Date) {
					B = B.getTime();
				}
                else if(B && (isNaN(parseFloat(B)) || !isFinite(B))) {
                    B = B.toLowerCase();
                }

                if (A > B) {
                    return -1
                } else if (A < B) {
                    return 1;
                } else {
                    return 0;
                }
            });

            return this;
        }

        // Find a subset of items based on a given closure; function(value, index, array)
        // subset of items returned as a new Collection instance.
        // Differs from .filter in that it does not modify current instance.
        Collection.prototype.subset = function(closure) {
            return new Collection(this.raw_data.filter(closure));
        }

        // Calculate the sum of all values of the given property name
        // in the collection.
        Collection.prototype.sumBy = function(sum_property) {
            var sum = 0;
            if (this.empty()) return sum;
            var i;
            for (i in this.raw_data) {
                sum = sum + parseFloat(this.raw_data[i][sum_property]);
            }
            return sum;
        }

        // Run provided closure against each element in the collection
        Collection.prototype.transform = function(closure) {
            this.raw_data.forEach(closure);
            return this;
        }

        return Collection;
    });<|endoftext|>


<commit_before>/**
 * Common Collection operations, use this if you have an array
 * of objects that you want to perform many operations on
**/
angular.module("ngCollection", [])
	.factory('Collection', function() {

        // Constructure, expects an array of objects
        function Collection(data_array) {
            if (Object.prototype.toString.call(data_array) !== '[object Array]') data_array = [];
            this.raw_data = data_array;
        }

        // Return the total number of elements in the collection
        Collection.prototype.count = function() {
            return this.raw_data.length;
        }

        // Return whether or not the collection is empty
        Collection.prototype.empty = function() {
            return this.count() <= 0;
        }

        // Return the first element in the collection
        Collection.prototype.first = function() {
            return this.empty() ? null : this.raw_data[0];
        }

        // Return the raw array of items
        Collection.prototype.items = function() {
            return this.raw_data;
        }

        // Return the last element in the collection
        Collection.prototype.last = function() {
            return this.empty() ? null : this.raw_data[this.raw_data.length - 1];
        }

        // Push a new element to the collection
        Collection.prototype.push = function(new_item) {
            this.raw_data.push(new_item);
            return this;
        }

        // Return the overall Collection average of a given property.
        // Optionally ignore elements where the group property value is
        // equal to zero.
        Collection.prototype.averageBy = function(avg_property, exclude_zeros) {

            if(exclude_zeros) {

                return this.subset(function(v, i, a) {
                    return a[i][avg_property] > 0;
                }).averageBy(avg_property);

            }

            return this.empty() ? 0 : (this.sumBy(avg_property) / this.count());
        }

        // Return an array where the indices correspond to the unique values of
        // the given group property and the values are the number of times that
        // value was found in the original Collection. Optionally ignore elements
        // where the group property value is empty.
        Collection.prototype.countBy = function(group_property, exclude_empty_values) {
            var counts = {};
            var i;
            if (this.empty()) return counts;
            for (i in this.raw_data) {
                if (exclude_empty_values && this.raw_data[i][group_property] == '') continue;
                if (!counts.hasOwnProperty(this.raw_data[i][group_property])) {
                    counts[this.raw_data[i][group_property]] = 0;
                }
                counts[this.raw_data[i][group_property]]++;
            }
            return counts;
        }

        // Return an array of unique values for the given property. Optionally
        // ignore elements where the fetch property value is empty.
        Collection.prototype.fetchUniqueValues = function(fetch_property, exclude_empty_values) {
            return $.map(this.countBy(fetch_property, exclude_empty_values), function (v,i) { return i; });
        }

        // Reduce the current collection to only those items that satisfy the
        // provided closure; function(value, index, array)
        // See .subset to get a subset of items without modifying original instance
        Collection.prototype.filter = function(closure) {
            this.raw_data = this.raw_data.filter(closure);
            return this;
        }

        // Return an array of Collections where the indices correspond to the unique
        // values of the given group property. Optionally ignore elements where the
        // group property value is empty.
        Collection.prototype.groupBy = function(group_property, exclude_empty_values) {
            var sorted = {};
            var i;
            if (this.empty()) return sorted;
            for (i in this.raw_data) {
                if (exclude_empty_values && this.raw_data[i][group_property] == '') continue;
                if (!sorted.hasOwnProperty(this.raw_data[i][group_property])) {
                    sorted[this.raw_data[i][group_property]] = new Collection();
                }
                sorted[this.raw_data[i][group_property]].push(this.raw_data[i]);
            }
            return sorted;
        }

        // Find the maximum value of the given property name in the Collection
        Collection.prototype.max = function(property) {
            return Math.max.apply(Math, this.fetchUniqueValues(property));
        }

		// Find the minimum value of the given property name in the Collection
        Collection.prototype.min = function(property) {
            return Math.min.apply(Math, this.fetchUniqueValues(property));
        }

        /**
         * Given a property name and a val (or array of vals), pull the item(s)
         * that match the given val(s) for that property from the array.
         * Note: this will remove any matches from the collection
         *
         * TODO: Create a fetchByVal that does something similar but doesn't
         *       modify the collection
         *
         * Returns:
         *   if a single, non-array value is requested and found, that item (object)
         *   if a single, non-array value is requested and not found, null
         *   if an array of values is requested, a Collection is always returned (even if empty)
         **/
        Collection.prototype.pullByVal = function(property, vals) {
            var multiples = vals instanceof Array;
            var result = (multiples ? new Collection() : null);

            if (multiples) {
                var valObj = {}; // make val list an object for quicker searching
                vals.forEach(function(v) { valObj[v] = 1; });
            }

            this.raw_data.forEach(function(v, i, a) {
                if (multiples) {
                    if (valObj.hasOwnProperty(v[property])) {
                        result.push(v);
                        a.splice(i,1);
                    }
                }
                else if (v[property] === vals) {
                    result = v;
                    a.splice(i,1);
                }
            });

            return result;
        }

        // Sort the current collection instance by the given property name
        // Note: this currently always sorts ascending and is case INsensitive
		// TODO: sort descending . . . and case sensitive sorting?
        Collection.prototype.sortBy = function(sort_property) {
            this.raw_data.sort(function(a,b) {
                var A = a[sort_property];
                var B = b[sort_property];

                // Make strings lowercase (case insensitive)
                if(isNaN(parseFloat(A)) || !isFinite(A)) {
                    A = A.toLowerCase();
                }

                if(isNaN(parseFloat(B)) || !isFinite(B)) {
                    B = B.toLowerCase();
                }

                if (A < B) {
                    return -1
                } else if (A > B) {
                    return 1;
                } else {
                    return 0;
                }
            });

            return this;
        }

        // Find a subset of items based on a given closure; function(value, index, array)
        // subset of items returned as a new Collection instance.
        // Differs from .filter in that it does not modify current instance.
        Collection.prototype.subset = function(closure) {
            return new Collection(this.raw_data.filter(closure));
        }

        // Calculate the sum of all values of the given property name
        // in the collection.
        Collection.prototype.sumBy = function(sum_property) {
            var sum = 0;
            if (this.empty()) return sum;
            var i;
            for (i in this.raw_data) {
                sum = sum + parseFloat(this.raw_data[i][sum_property]);
            }
            return sum;
        }

        // Run provided closure against each element in the collection
        Collection.prototype.transform = function(closure) {
            this.raw_data.forEach(closure);
            return this;
        }

        return Collection;
    });<commit_msg>Fix countBy and sortBy to work with boolean values.<commit_after>/**
 * Common Collection operations, use this if you have an array
 * of objects that you want to perform many operations on
**/
angular.module("ngCollection", [])
	.factory('Collection', function() {

        // Constructure, expects an array of objects
        function Collection(data_array) {
            if (Object.prototype.toString.call(data_array) !== '[object Array]') data_array = [];
            this.raw_data = data_array;
        }

        // Return the total number of elements in the collection
        Collection.prototype.count = function() {
            return this.raw_data.length;
        }

        // Return whether or not the collection is empty
        Collection.prototype.empty = function() {
            return this.count() <= 0;
        }

        // Return the first element in the collection
        Collection.prototype.first = function() {
            return this.empty() ? null : this.raw_data[0];
        }

        // Return the raw array of items
        Collection.prototype.items = function() {
            return this.raw_data;
        }

        // Return the last element in the collection
        Collection.prototype.last = function() {
            return this.empty() ? null : this.raw_data[this.raw_data.length - 1];
        }

        // Push a new element to the collection
        Collection.prototype.push = function(new_item) {
            this.raw_data.push(new_item);
            return this;
        }

        // Return the overall Collection average of a given property.
        // Optionally ignore elements where the group property value is
        // equal to zero.
        Collection.prototype.averageBy = function(avg_property, exclude_zeros) {

            if(exclude_zeros) {

                return this.subset(function(v, i, a) {
                    return a[i][avg_property] > 0;
                }).averageBy(avg_property);

            }

            return this.empty() ? 0 : (this.sumBy(avg_property) / this.count());
        }

        // Return an array where the indices correspond to the unique values of
        // the given group property and the values are the number of times that
        // value was found in the original Collection. Optionally ignore elements
        // where the group property value is empty.
        Collection.prototype.countBy = function(group_property, exclude_empty_values) {
            var counts = {};
            var i;
            if (this.empty()) return counts;
            for (i in this.raw_data) {
				var key = this.raw_data[i][group_property];
				if (key === true || key === false) key = key ? 1 : 0;
                if (exclude_empty_values && key == '') continue;
                if (!counts.hasOwnProperty(key)) {
                    counts[key] = 0;
                }
                counts[key]++;
            }
            return counts;
        }

        // Return an array of unique values for the given property. Optionally
        // ignore elements where the fetch property value is empty.
        Collection.prototype.fetchUniqueValues = function(fetch_property, exclude_empty_values) {
            return $.map(this.countBy(fetch_property, exclude_empty_values), function (v,i) { return i; });
        }

        // Reduce the current collection to only those items that satisfy the
        // provided closure; function(value, index, array)
        // See .subset to get a subset of items without modifying original instance
        Collection.prototype.filter = function(closure) {
            this.raw_data = this.raw_data.filter(closure);
            return this;
        }

        // Return an array of Collections where the indices correspond to the unique
        // values of the given group property. Optionally ignore elements where the
        // group property value is empty.
        Collection.prototype.groupBy = function(group_property, exclude_empty_values) {
            var sorted = {};
            var i;
            if (this.empty()) return sorted;
            for (i in this.raw_data) {
				var key = this.raw_data[i][group_property];
				if (key === true || key === false) key = key ? 1 : 0;

                if (exclude_empty_values && this.raw_data[i][group_property] == '') continue;
                if (!sorted.hasOwnProperty(key)) {
                    sorted[key] = new Collection();
                }
                sorted[key].push(this.raw_data[i]);
            }
            return sorted;
        }

        // Find the maximum value of the given property name in the Collection
        Collection.prototype.max = function(property) {
            return Math.max.apply(Math, this.fetchUniqueValues(property));
        }

		// Find the minimum value of the given property name in the Collection
        Collection.prototype.min = function(property) {
            return Math.min.apply(Math, this.fetchUniqueValues(property));
        }

        /**
         * Given a property name and a val (or array of vals), pull the item(s)
         * that match the given val(s) for that property from the array.
         * Note: this will remove any matches from the collection
         *
         * TODO: Create a fetchByVal that does something similar but doesn't
         *       modify the collection
         *
         * Returns:
         *   if a single, non-array value is requested and found, that item (object)
         *   if a single, non-array value is requested and not found, null
         *   if an array of values is requested, a Collection is always returned (even if empty)
         **/
        Collection.prototype.pullByVal = function(property, vals) {
            var multiples = vals instanceof Array;
            var result = (multiples ? new Collection() : null);

            if (multiples) {
                var valObj = {}; // make val list an object for quicker searching
                vals.forEach(function(v) { valObj[v] = 1; });
            }

            this.raw_data.forEach(function(v, i, a) {
                if (multiples) {
                    if (valObj.hasOwnProperty(v[property])) {
                        result.push(v);
                        a.splice(i,1);
                    }
                }
                else if (v[property] === vals) {
                    result = v;
                    a.splice(i,1);
                }
            });

            return result;
        }

        // Sort the current collection instance by the given property name
        // Note: this currently always sorts ascending and is case INsensitive
		// TODO: sort descending . . . and case sensitive sorting?
        Collection.prototype.sortBy = function(sort_property) {
            this.raw_data.sort(function(a,b) {
                var A = a[sort_property];
                var B = b[sort_property];

                // Make strings lowercase (case insensitive)
                if(isNaN(parseFloat(A)) || !isFinite(A)) {
                    A = A.toLowerCase();
                }

                if(isNaN(parseFloat(B)) || !isFinite(B)) {
                    B = B.toLowerCase();
                }

                if (A < B) {
                    return -1
                } else if (A > B) {
                    return 1;
                } else {
                    return 0;
                }
            });

            return this;
        }

        // Find a subset of items based on a given closure; function(value, index, array)
        // subset of items returned as a new Collection instance.
        // Differs from .filter in that it does not modify current instance.
        Collection.prototype.subset = function(closure) {
            return new Collection(this.raw_data.filter(closure));
        }

        // Calculate the sum of all values of the given property name
        // in the collection.
        Collection.prototype.sumBy = function(sum_property) {
            var sum = 0;
            if (this.empty()) return sum;
            var i;
            for (i in this.raw_data) {
                sum = sum + parseFloat(this.raw_data[i][sum_property]);
            }
            return sum;
        }

        // Run provided closure against each element in the collection
        Collection.prototype.transform = function(closure) {
            this.raw_data.forEach(closure);
            return this;
        }

        return Collection;
    });<|endoftext|>


<commit_before>(function() {
	var noticationContainerEL = $('#js-alerts-container');

	function addNotification(msg) {
		// var n = document.createElement('div');
		// div.textContent = msg;
		// noticationContainerEL.appendChild(n);
		noticationContainerEL.textContent = msg;
		noticationContainerEL.classList.add('is-active');

		setTimeout(function() {
			noticationContainerEL.classList.remove('is-active');
		}, 2000);
	}

	window.alertsService = {
		add: addNotification
	};
})();
<commit_msg>notifications.js: prevent multiple hide timeouts from getting added.<commit_after>(function() {
	const noticationContainerEL = $('#js-alerts-container');
	var hideTimeout;

	function addNotification(msg) {
		// var n = document.createElement('div');
		// div.textContent = msg;
		// noticationContainerEL.appendChild(n);
		noticationContainerEL.textContent = msg;
		noticationContainerEL.classList.add('is-active');

		clearTimeout(hideTimeout);
		hideTimeout = setTimeout(function() {
			noticationContainerEL.classList.remove('is-active');
		}, 2000);
	}

	window.alertsService = {
		add: addNotification
	};
})();
<|endoftext|>


<commit_before>module.exports = function (dex) {
  Function.prototype.clone = function() {
    var fct = this;
    var clone = function() {
      return fct.apply(this, arguments);
    };
    clone.prototype = fct.prototype;
    for (property in fct) {
      if (fct.hasOwnProperty(property) && property !== 'prototype') {
        clone[property] = fct[property];
      }
    }
    return clone;
  };

  /**
   *
   * This module provides routines for dealing with objects.
   *
   * @module dex/object
   *
   */
  var object = {};
  /**
   *
   * Return the local keys of this object without the inherited ones.
   *
   * @param obj The object whose local keys we are interested in.
   *
   * @returns {Array} An array of 0 or more local keys.
   *
   * @memberof dex/object
   *
   */
  object.keys = function keys(obj) {
    var keys = [];

    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        keys.push(key);
      }
    }

    return keys;
  };

  object.visit = function visit(obj, visitor) {
    visitor(obj);
    object.keys(obj).forEach(function(key) {
      //dex.console.log("Visiting(" + key + ")" + " of type: '" + (typeof obj[key]) + "'");
      if (obj[key] !== undefined && ((typeof obj[key]) === "object")) {
        dex.object.visit(obj[key], visitor);
      }
    });
  };

  /**
   *
   * A pretty good, but imperfect mechanism for performing a deep
   * clone of an object.
   *
   * @param obj The object to clone.
   * @returns {*} The cloned object.
   *
   * @memberof dex/object
   *
   */
  object.clone = function clone(obj) {
    var copy;

    if (obj === undefined || obj === null) {
      return obj;
    }

    // Ducktyped CSV.
    if (obj.data !== undefined && obj.header !== undefined) {
      // Return a full deep copy
      return new dex.csv(obj);
    }

    // Handle the 3 simple types, and null or undefined
    //if (null == obj || "object" != typeof obj) return obj;

    switch (typeof obj) {
      case "string":
      case "number":
      case "boolean": {
        copy = obj;
        return copy;
      }
      case "function": {
        return obj.clone();
      }
    }

    //dex.console.log("OBJ= type(" + typeof obj + "), value('" + obj + "')", obj,
    //  obj instanceof Date, obj instanceof Object, obj instanceof Array);

    // Handle Date
    if (obj instanceof Date) {
      copy = new Date();
      copy.setTime(obj.getTime());
      return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
      copy = [];
      for (var i = 0, len = obj.length; i < len; i++) {
        copy[i] = dex.object.clone(obj[i]);
      }
      return copy;
    }

    // DOM Nodes and dex.js components are only shallow copies.
    if (dex.object.isElement(obj) || dex.object.isNode(obj) || dex.object.isComponent(obj)) {
      return obj;
    }

    // Handle Object
    if (obj instanceof Object) {
      copy = {};
      for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = dex.object.clone(obj[attr]);
      }
      return copy;
    }

    throw new Error("Error COPYING: obj, type", obj, typeof obj);
  };

  /**
   *
   * Kind of misleading.  This really signals when expand should quit
   * expanding.  I need to clean this up.
   *
   * @param obj
   * @returns {boolean}
   *
   * @memberof dex/object
   *
   */
  object.isEmpty = function isEmpty(obj) {
    //dex.console.log("isEmpty(" + obj + ") typeof=" + (typeof obj));
    if (!obj || obj instanceof Array) {
      return true;
    }
    if ("object" == typeof obj) {
      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          //dex.console.log("OBJ: ", obj, " contains key '" + key + "'");
          return false;
        }
      }
    }

    return true;
  };

  /**
   *
   * Overlay the top object on top of the bottom.  This method will first clone
   * the bottom object.  Then it will drop the values within the top object
   * into the clone.
   *
   * @param {Object} top - The object who's properties will be on top.
   * @param {Object} bottom - The object who's properties will be on bottom.
   * @return {Object} The overlaid object where the properties in top override
   *                  properties in bottom.  The return object is a clone or
   *                  copy.
   *
   * @memberof dex/object
   *
   */
  object.overlay = function overlay(top, bottom) {
    // Make a clone of the bottom object.
    var overlay = dex.object.clone(bottom);
    var prop;

    // If we have parameters in the top object, overlay them on top
    // of the bottom object.
    if (top !== 'undefined') {
      // Iterate over the props in top.
      for (prop in top) {
        // Arrays are special cases. [A] on top of [A,B] should give [A], not [A,B]
        if (top[prop] instanceof Array) {
          overlay[prop] = dex.array.copy(top[prop]);
        }
        else if ((top[prop] !== undefined && top[prop] != null &&
            top[prop].constructor !== undefined &&
            top[prop].constructor.name === "csv")) {
          overlay[prop] = new dex.csv(top[prop]);
        }
        else if (typeof top[prop] == 'object' && overlay[prop] != null) {
          //console.log("PROP: " + prop + ", top=" + top + ", overlay=" + overlay);
          overlay[prop] = dex.object.overlay(top[prop], overlay[prop]);
        }
        // Simply overwrite for simple cases and arrays.
        else {
          overlay[prop] = top[prop];
        }
      }
    }

    //console.dir(config);
    return overlay;
  };

  /**
   *
   * This method returns whether or not the supplied object is a Node.
   *
   * @param {Object} obj - The object to test.
   *
   * @returns {boolean} True if obj is a Node, false otherwise.
   *
   * @memberof dex/object
   *
   */
  object.isNode = function isNode(obj) {
    return (
      typeof Node === "object" ? obj instanceof Node :
        obj && typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string"
    );
  };

  object.isComponent = function isNode(obj) {
    return (obj instanceof dex.component);
  };

  /**
   *
   * This method returns whether or not the supplied object is a
   * DOM node.
   *
   * @param {Object} obj - The object to test.
   *
   * @returns {boolean} - True if obj is a DOM node, false otherwise.
   *
   * @memberof dex/object
   *
   */
  object.isElement = function isElement(obj) {
    return (
      typeof HTMLElement === "object" ? obj instanceof HTMLElement : //DOM2
        obj && typeof obj === "object" && obj.nodeType === 1 && typeof obj.nodeName === "string"
    );
  };

  /**
   *
   * This method returns a boolean representing whether obj is contained
   * within container.
   *
   * @param {Object} container - The container to test.
   * @param {Object} obj - The object to test.
   *
   * @return True if container contains obj.  False otherwise.
   *
   * @memberof dex/object
   *
   */
  object.contains = function contains(container, obj) {
    var i = container.length;
    while (i--) {
      if (container[i] === obj) {
        return true;
      }
    }
    return false;
  };

  /**
   *
   * Return whether or not the supplied object is a function.
   *
   * @param obj The object to check.
   * @returns {boolean} True if obj is a function, false otherwise.
   *
   * @memberof dex/object
   *
   */
  object.isFunction = function isFunction(obj) {
    //return typeof obj === 'function';
    return (typeof obj === "function");
  };

  object.couldBeADate = function (str) {
    if (typeof str === "string") {
      var d = dex.moment(str);
      if (d == null || !d.isValid()) return false;

      return true;
    }
    return typeof str == "date";
  };

  /**
   *
   * @param map
   * @param values
   * @returns {exports}
   *
   * @memberof dex/object
   *
   */
  object.connect = function connect(map, values) {
    //dex.console.log("map:", map, "values:", values);

    if (!values || values.length <= 0) {
      return this;
    }
    if (!map[values[0]]) {
      map[values[0]] = {};
    }
    dex.object.connect(map[values[0]], values.slice(1));

    return this;
  };

  /**
   *
   * @param obj
   * @returns {boolean}
   *
   * @memberof dex/object
   *
   */
  object.isNumeric = function (obj) {
    return !isNaN(parseFloat(obj)) && isFinite(obj);
  };

  /**
   *
   * @param hierarchy
   * @param name
   * @param value
   * @param delimiter
   * @returns {*}
   *
   * @memberof dex/object
   *
   */
  object.setHierarchical = function (hierarchy, name, value, delimiter) {
    if (hierarchy == null) {
      hierarchy = {};
    }

    if (typeof hierarchy != 'object') {
      return hierarchy;
    }

    // Create an array of names by splitting delimiter, then call
    // this function in the 3 argument (Array of paths) context.
    if (arguments.length == 4) {
      return dex.object.setHierarchical(hierarchy,
        name.split(delimiter), value);
    }

    // Array of paths context.
    else {
      // This is the last variable name, just set the value.
      if (name.length === 1) {
        hierarchy[name[0]] = value;
      }
      // We still have to traverse.
      else {
        // Undefined container object, just create an empty.
        if (!(name[0] in hierarchy)) {
          hierarchy[name[0]] = {};
        }

        // Recursively traverse down the hierarchy.
        dex.object.setHierarchical(hierarchy[name[0]], name.splice(1), value);
      }
    }

    return hierarchy;
  };

  object.isArray = function (obj) {
    return Array.isArray(obj);
  };

  object.getHierarchical = function (hierarchy, name) {
    //dex.console.log("getHierarchical", hierarchy, name);
    if ((typeof hierarchy) == "undefined" ||
      (typeof name == "undefined")) {
      return undefined;
    }
    var nsIndex = name.indexOf(".");
    if (nsIndex >= 0) {
      var key = name.substring(0, nsIndex);
      var remainingKey = name.substring(nsIndex + 1);
      //dex.console.log("NAME: ", key, remainingKey);
      return dex.object.getHierarchical(hierarchy[key], remainingKey);
    }
    else {
      return hierarchy[name];
    }
  };

  object.getValue = function (hierarchy, name, defaultValue) {
    return dex.object.getHierarchical(hierarchy, name) || defaultValue;
  };
  return object;
};

<commit_msg>Date parsing changes plus visitor changes<commit_after>module.exports = function (dex) {
  Function.prototype.clone = function() {
    var fct = this;
    var clone = function() {
      return fct.apply(this, arguments);
    };
    clone.prototype = fct.prototype;
    for (property in fct) {
      if (fct.hasOwnProperty(property) && property !== 'prototype') {
        clone[property] = fct[property];
      }
    }
    return clone;
  };

  /**
   *
   * This module provides routines for dealing with objects.
   *
   * @module dex/object
   *
   */
  var object = {};
  /**
   *
   * Return the local keys of this object without the inherited ones.
   *
   * @param obj The object whose local keys we are interested in.
   *
   * @returns {Array} An array of 0 or more local keys.
   *
   * @memberof dex/object
   *
   */
  object.keys = function keys(obj) {
    var keys = [];

    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        keys.push(key);
      }
    }

    return keys;
  };

  object.visit = function visit(obj, visitor) {
    if (obj !== undefined && visitor !== undefined) {
      visitor(obj);
      object.keys(obj).forEach(function (key) {
        //dex.console.log("Visiting(" + key + ")" + " of type: '" + (typeof obj[key]) + "'");
        if (obj[key] !== undefined && ((typeof obj[key]) === "object")) {
          dex.object.visit(obj[key], visitor);
        }
      });
    }
  };

  /**
   *
   * A pretty good, but imperfect mechanism for performing a deep
   * clone of an object.
   *
   * @param obj The object to clone.
   * @returns {*} The cloned object.
   *
   * @memberof dex/object
   *
   */
  object.clone = function clone(obj) {
    var copy;

    if (obj === undefined || obj === null) {
      return obj;
    }

    // Ducktyped CSV.
    if (obj.data !== undefined && obj.header !== undefined) {
      // Return a full deep copy
      return new dex.csv(obj);
    }

    // Handle the 3 simple types, and null or undefined
    //if (null == obj || "object" != typeof obj) return obj;

    switch (typeof obj) {
      case "string":
      case "number":
      case "boolean": {
        copy = obj;
        return copy;
      }
      case "function": {
        return obj.clone();
      }
    }

    //dex.console.log("OBJ= type(" + typeof obj + "), value('" + obj + "')", obj,
    //  obj instanceof Date, obj instanceof Object, obj instanceof Array);

    // Handle Date
    if (obj instanceof Date) {
      copy = new Date();
      copy.setTime(obj.getTime());
      return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
      copy = [];
      for (var i = 0, len = obj.length; i < len; i++) {
        copy[i] = dex.object.clone(obj[i]);
      }
      return copy;
    }

    // DOM Nodes and dex.js components are only shallow copies.
    if (dex.object.isElement(obj) || dex.object.isNode(obj) || dex.object.isComponent(obj)) {
      return obj;
    }

    // Handle Object
    if (obj instanceof Object) {
      copy = {};
      for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = dex.object.clone(obj[attr]);
      }
      return copy;
    }

    throw new Error("Error COPYING: obj, type", obj, typeof obj);
  };

  /**
   *
   * Kind of misleading.  This really signals when expand should quit
   * expanding.  I need to clean this up.
   *
   * @param obj
   * @returns {boolean}
   *
   * @memberof dex/object
   *
   */
  object.isEmpty = function isEmpty(obj) {
    //dex.console.log("isEmpty(" + obj + ") typeof=" + (typeof obj));
    if (!obj || obj instanceof Array) {
      return true;
    }
    if ("object" == typeof obj) {
      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          //dex.console.log("OBJ: ", obj, " contains key '" + key + "'");
          return false;
        }
      }
    }

    return true;
  };

  /**
   *
   * Overlay the top object on top of the bottom.  This method will first clone
   * the bottom object.  Then it will drop the values within the top object
   * into the clone.
   *
   * @param {Object} top - The object who's properties will be on top.
   * @param {Object} bottom - The object who's properties will be on bottom.
   * @return {Object} The overlaid object where the properties in top override
   *                  properties in bottom.  The return object is a clone or
   *                  copy.
   *
   * @memberof dex/object
   *
   */
  object.overlay = function overlay(top, bottom) {
    // Make a clone of the bottom object.
    var overlay = dex.object.clone(bottom);
    var prop;

    // If we have parameters in the top object, overlay them on top
    // of the bottom object.
    if (top !== 'undefined') {
      // Iterate over the props in top.
      for (prop in top) {
        // Arrays are special cases. [A] on top of [A,B] should give [A], not [A,B]
        if (top[prop] instanceof Array) {
          overlay[prop] = dex.array.copy(top[prop]);
        }
        else if ((top[prop] !== undefined && top[prop] != null &&
            top[prop].constructor !== undefined &&
            top[prop].constructor.name === "csv")) {
          overlay[prop] = new dex.csv(top[prop]);
        }
        else if (typeof top[prop] == 'object' && overlay[prop] != null) {
          //console.log("PROP: " + prop + ", top=" + top + ", overlay=" + overlay);
          overlay[prop] = dex.object.overlay(top[prop], overlay[prop]);
        }
        // Simply overwrite for simple cases and arrays.
        else {
          overlay[prop] = top[prop];
        }
      }
    }

    //console.dir(config);
    return overlay;
  };

  /**
   *
   * This method returns whether or not the supplied object is a Node.
   *
   * @param {Object} obj - The object to test.
   *
   * @returns {boolean} True if obj is a Node, false otherwise.
   *
   * @memberof dex/object
   *
   */
  object.isNode = function isNode(obj) {
    return (
      typeof Node === "object" ? obj instanceof Node :
        obj && typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string"
    );
  };

  object.isComponent = function isNode(obj) {
    return (obj instanceof dex.component);
  };

  /**
   *
   * This method returns whether or not the supplied object is a
   * DOM node.
   *
   * @param {Object} obj - The object to test.
   *
   * @returns {boolean} - True if obj is a DOM node, false otherwise.
   *
   * @memberof dex/object
   *
   */
  object.isElement = function isElement(obj) {
    return (
      typeof HTMLElement === "object" ? obj instanceof HTMLElement : //DOM2
        obj && typeof obj === "object" && obj.nodeType === 1 && typeof obj.nodeName === "string"
    );
  };

  /**
   *
   * This method returns a boolean representing whether obj is contained
   * within container.
   *
   * @param {Object} container - The container to test.
   * @param {Object} obj - The object to test.
   *
   * @return True if container contains obj.  False otherwise.
   *
   * @memberof dex/object
   *
   */
  object.contains = function contains(container, obj) {
    var i = container.length;
    while (i--) {
      if (container[i] === obj) {
        return true;
      }
    }
    return false;
  };

  /**
   *
   * Return whether or not the supplied object is a function.
   *
   * @param obj The object to check.
   * @returns {boolean} True if obj is a function, false otherwise.
   *
   * @memberof dex/object
   *
   */
  object.isFunction = function isFunction(obj) {
    //return typeof obj === 'function';
    return (typeof obj === "function");
  };

  object.couldBeADate = function (str) {
    if (typeof str === "string") {
      try {
        var d = dex.moment(str);
        if (d == null || !d.isValid()) return false;
      }
      catch (err) {
        return false;
      }
      return true;
    }
    return typeof str == "date";
  };

  /**
   *
   * @param map
   * @param values
   * @returns {exports}
   *
   * @memberof dex/object
   *
   */
  object.connect = function connect(map, values) {
    //dex.console.log("map:", map, "values:", values);

    if (!values || values.length <= 0) {
      return this;
    }
    if (!map[values[0]]) {
      map[values[0]] = {};
    }
    dex.object.connect(map[values[0]], values.slice(1));

    return this;
  };

  /**
   *
   * @param obj
   * @returns {boolean}
   *
   * @memberof dex/object
   *
   */
  object.isNumeric = function (obj) {
    return !isNaN(parseFloat(obj)) && isFinite(obj);
  };

  /**
   *
   * @param hierarchy
   * @param name
   * @param value
   * @param delimiter
   * @returns {*}
   *
   * @memberof dex/object
   *
   */
  object.setHierarchical = function (hierarchy, name, value, delimiter) {
    if (hierarchy == null) {
      hierarchy = {};
    }

    if (typeof hierarchy != 'object') {
      return hierarchy;
    }

    // Create an array of names by splitting delimiter, then call
    // this function in the 3 argument (Array of paths) context.
    if (arguments.length == 4) {
      return dex.object.setHierarchical(hierarchy,
        name.split(delimiter), value);
    }

    // Array of paths context.
    else {
      // This is the last variable name, just set the value.
      if (name.length === 1) {
        hierarchy[name[0]] = value;
      }
      // We still have to traverse.
      else {
        // Undefined container object, just create an empty.
        if (!(name[0] in hierarchy)) {
          hierarchy[name[0]] = {};
        }

        // Recursively traverse down the hierarchy.
        dex.object.setHierarchical(hierarchy[name[0]], name.splice(1), value);
      }
    }

    return hierarchy;
  };

  object.isArray = function (obj) {
    return Array.isArray(obj);
  };

  object.getHierarchical = function (hierarchy, name) {
    //dex.console.log("getHierarchical", hierarchy, name);
    if ((typeof hierarchy) == "undefined" ||
      (typeof name == "undefined")) {
      return undefined;
    }
    var nsIndex = name.indexOf(".");
    if (nsIndex >= 0) {
      var key = name.substring(0, nsIndex);
      var remainingKey = name.substring(nsIndex + 1);
      //dex.console.log("NAME: ", key, remainingKey);
      return dex.object.getHierarchical(hierarchy[key], remainingKey);
    }
    else {
      return hierarchy[name];
    }
  };

  object.getValue = function (hierarchy, name, defaultValue) {
    return dex.object.getHierarchical(hierarchy, name) || defaultValue;
  };
  return object;
};

<|endoftext|>