<commit_before>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define */

define(function (require, exports, module) {
    "use strict";

    var FileSystemError = require("filesystem/FileSystemError");

    var VISIT_DEFAULT_MAX_DEPTH = 100,
        VISIT_DEFAULT_MAX_ENTRIES = 30000;

    /* Counter to give every entry a unique id */
    var nextId = 0;

    /**
     * @constructor
     * Model for a file system entry. This is the base class for File and Directory,
     * and is never used directly.
     *
     * See the File, Directory, and FileSystem classes for more details.
     *
     * @param {string} path The path for this entry.
     * @param {FileSystem} fileSystem The file system associated with this entry.
     */
    function FileSystemEntry(path, fileSystem) {
        this._setPath(path);
        this._fileSystem = fileSystem;
        this._id = nextId++;
    }

    // Add "fullPath", "name", "parent", "id", "isFile" and "isDirectory" getters
    Object.defineProperties(FileSystemEntry.prototype, {
        "fullPath": {
            get: function () { return this._path; },
            set: function () { throw new Error("Cannot set fullPath"); }
        },
        "name": {
            get: function () { return this._name; },
            set: function () { throw new Error("Cannot set name"); }
        },
        "parentPath": {
            get: function () { return this._parentPath; },
            set: function () { throw new Error("Cannot set parentPath"); }
        },
        "id": {
            get: function () { return this._id; },
            set: function () { throw new Error("Cannot set id"); }
        },
        "isFile": {
            get: function () { return this._isFile; },
            set: function () { throw new Error("Cannot set isFile"); }
        },
        "isDirectory": {
            get: function () { return this._isDirectory; },
            set: function () { throw new Error("Cannot set isDirectory"); }
        },
        "_impl": {
            get: function () { return this._fileSystem._impl; },
            set: function () { throw new Error("Cannot set _impl"); }
        }
    });

    /**
     * Cached stat object for this file.
     * @type {?FileSystemStats}
     */
    FileSystemEntry.prototype._stat = null;

    /**
     * Parent file system.
     * @type {!FileSystem}
     */
    FileSystemEntry.prototype._fileSystem = null;

    /**
     * The path of this entry.
     * @type {string}
     */
    FileSystemEntry.prototype._path = null;

    /**
     * The name of this entry.
     * @type {string}
     */
    FileSystemEntry.prototype._name = null;

    /**
     * The parent of this entry.
     * @type {string}
     */
    FileSystemEntry.prototype._parentPath = null;

    /**
     * Whether or not the entry is a file
     * @type {boolean}
     */
    FileSystemEntry.prototype._isFile = false;

    /**
     * Whether or not the entry is a directory
     * @type {boolean}
     */
    FileSystemEntry.prototype._isDirectory = false;

    /**
     * Whether or not the entry is watched.
     * @type {boolean}
     */
    FileSystemEntry.prototype._isWatched = false;

    /**
     * Update the path for this entry
     * @private
     * @param {String} newPath
     */
    FileSystemEntry.prototype._setPath = function (newPath) {
        var parts = newPath.split("/");
        if (this.isDirectory) {
            parts.pop(); // Remove the empty string after last trailing "/"
        }
        this._name = parts[parts.length - 1];
        parts.pop(); // Remove name

        if (parts.length > 0) {
            this._parentPath = parts.join("/") + "/";
        } else {
            // root directories have no parent path
            this._parentPath = null;
        }

        this._path = newPath;
    };

    /**
     * Mark this entry as being watched after construction. There is no way to
     * set an entry as being unwatched after construction because entries should
     * be discarded upon being unwatched.
     * @private
     */
    FileSystemEntry.prototype._setWatched = function () {
        this._isWatched = true;
    };

    /**
     * Clear any cached data for this entry
     * @private
     */
    FileSystemEntry.prototype._clearCachedData = function () {
        this._stat = undefined;
    };

    /**
     * Helpful toString for debugging purposes
     */
    FileSystemEntry.prototype.toString = function () {
        return "[" + (this.isDirectory ? "Directory " : "File ") + this._path + "]";
    };

    /**
     * Check to see if the entry exists on disk. Note that there will NOT be an
     * error returned if the file does not exist on the disk; in that case the
     * error parameter will be null and the boolean will be false. The error
     * parameter will only be truthy when an unexpected error was encountered
     * during the test, in which case the state of the entry should be considered
     * unknown.
     *
     * @param {function (?string, boolean)} callback Callback with a FileSystemError
     *      string or a boolean indicating whether or not the file exists.
     */
    FileSystemEntry.prototype.exists = function (callback) {
        if (this._stat) {
            callback(null, true);
            return;
        }

        this._impl.exists(this._path, function (err, exists) {
            if (err) {
                this._clearCachedData();
                callback(err);
                return;
            }

            callback(null, exists);
        }.bind(this));
    };

    /**
     * Returns the stats for the entry.
     *
     * @param {function (?string, FileSystemStats=)} callback Callback with a
     *      FileSystemError string or FileSystemStats object.
     */
    FileSystemEntry.prototype.stat = function (callback) {
        if (this._stat && this._isWatched) {
            callback(null, this._stat);
            return;
        }

        this._impl.stat(this._path, function (err, stat) {
            if (err) {
                this._clearCachedData();
                callback(err);
                return;
            }

            this._stat = stat;

            callback(null, stat);
        }.bind(this));
    };

    /**
     * Rename this entry.
     *
     * @param {string} newFullPath New path & name for this entry.
     * @param {function (?string)=} callback Callback with a single FileSystemError
     *      string parameter.
     */
    FileSystemEntry.prototype.rename = function (newFullPath, callback) {
        callback = callback || function () {};
        this._fileSystem._beginWrite();
        this._impl.rename(this._path, newFullPath, function (err) {
            try {
                if (err) {
                    this._clearCachedData();
                    callback(err);
                    return;
                }

                try {
                    callback(null);  // notify caller
                } finally {
                    // Notify the file system of the name change
                    this._fileSystem._entryRenamed(this._path, newFullPath, this.isDirectory);
                }
            } finally {
                // Unblock external change events
                this._fileSystem._endWrite();
            }
        }.bind(this));
    };

    /**
     * Unlink (delete) this entry. For Directories, this will delete the directory
     * and all of its contents.
     *
     * @param {function (?string)=} callback Callback with a single FileSystemError
     *      string parameter.
     */
    FileSystemEntry.prototype.unlink = function (callback) {
        callback = callback || function () {};

        this._clearCachedData();

        this._impl.unlink(this._path, function (err) {
            try {
                callback(err);
            } finally {
                this._fileSystem._handleWatchResult(this._parentPath);
                this._fileSystem._index.removeEntry(this);
            }
        }.bind(this));
    };

    /**
     * Move this entry to the trash. If the underlying file system doesn't support move
     * to trash, the item is permanently deleted.
     *
     * @param {function (?string)=} callback Callback with a single FileSystemError
     *      string parameter.
     */
    FileSystemEntry.prototype.moveToTrash = function (callback) {
        if (!this._impl.moveToTrash) {
            this.unlink(callback);
            return;
        }

        callback = callback || function () {};

        this._clearCachedData();

        this._impl.moveToTrash(this._path, function (err) {
            try {
                callback(err);
            } finally {
                this._fileSystem._handleWatchResult(this._parentPath);
                this._fileSystem._index.removeEntry(this);
            }
        }.bind(this));
    };

    /**
     * Private helper function for FileSystemEntry.visit that requires sanitized options.
     *
     * @private
     * @param {FileSystemStats} stats - the stats for this entry
     * @param {{string: boolean}} visitedPaths - the set of fullPaths that have already been visited
     * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is
     *      applied to descendent FileSystemEntry objects. If the function returns false for
     *      a particular Directory entry, that directory's descendents will not be visited.
     * @param {{maxDepth: number, maxEntriesCounter: {value: number}}} options
     * @param {function(?string)=} callback Callback with single FileSystemError string parameter.
     */
    FileSystemEntry.prototype._visitHelper = function (stats, visitedPaths, visitor, options, callback) {
        var maxDepth = options.maxDepth,
            maxEntriesCounter = options.maxEntriesCounter;

        if (maxEntriesCounter.value-- <= 0 || maxDepth-- < 0) {
            // The outer FileSystemEntry.visit call is responsible for applying
            // the main callback to FileSystemError.TOO_MANY_FILES in this case
            callback(null);
            return;
        }

        if (this.isDirectory) {
            var visitedPath = stats.realPath || this.fullPath;

            if (visitedPaths.hasOwnProperty(visitedPath)) {
                // Link cycle detected
                callback(null);
                return;
            }

            visitedPaths[visitedPath] = true;
        }

        if (!visitor(this) || this.isFile) {
            callback(null);
            return;
        }

        this.getContents(function (err, entries, entriesStats) {
            if (err) {
                callback(err);
                return;
            }

            var counter = entries.length;
            if (counter === 0) {
                callback(null);
                return;
            }

            function helperCallback(err) {
                if (--counter === 0) {
                    callback(null);
                }
            }

            var nextOptions = {
                maxDepth: maxDepth,
                maxEntriesCounter: maxEntriesCounter
            };

            entries.forEach(function (entry, index) {
                var stats = entriesStats[index];
                entry._visitHelper(stats, visitedPaths, visitor, nextOptions, helperCallback);
            });
        }.bind(this));
    };

    /**
     * Visit this entry and its descendents with the supplied visitor function.
     *
     * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is
     *      applied to descendent FileSystemEntry objects. If the function returns false for
     *      a particular Directory entry, that directory's descendents will not be visited.
     * @param {{maxDepth: number=, maxEntries: number=}=} options
     * @param {function(?string)=} callback Callback with single FileSystemError string parameter.
     */
    FileSystemEntry.prototype.visit = function (visitor, options, callback) {
        if (typeof options === "function") {
            callback = options;
            options = {};
        } else if (options === undefined) {
            options = {};
        }

        if (options.maxDepth === undefined) {
            options.maxDepth = VISIT_DEFAULT_MAX_DEPTH;
        }

        if (options.maxEntries === undefined) {
            options.maxEntries = VISIT_DEFAULT_MAX_ENTRIES;
        }

        options.maxEntriesCounter = { value: options.maxEntries };

        this.stat(function (err, stats) {
            if (err) {
                callback(err);
                return;
            }

            this._visitHelper(stats, {}, visitor, options, function (err) {
                if (callback) {
                    if (err) {
                        callback(err);
                        return;
                    }

                    if (options.maxEntriesCounter.value < 0) {
                        callback(FileSystemError.TOO_MANY_ENTRIES);
                        return;
                    }

                    callback(null);
                }
            }.bind(this));
        }.bind(this));
    };

    // Export this class
    module.exports = FileSystemEntry;
});
<commit_msg>Block external changes around the execution of unlink and moveToTrash<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define */

define(function (require, exports, module) {
    "use strict";

    var FileSystemError = require("filesystem/FileSystemError");

    var VISIT_DEFAULT_MAX_DEPTH = 100,
        VISIT_DEFAULT_MAX_ENTRIES = 30000;

    /* Counter to give every entry a unique id */
    var nextId = 0;

    /**
     * @constructor
     * Model for a file system entry. This is the base class for File and Directory,
     * and is never used directly.
     *
     * See the File, Directory, and FileSystem classes for more details.
     *
     * @param {string} path The path for this entry.
     * @param {FileSystem} fileSystem The file system associated with this entry.
     */
    function FileSystemEntry(path, fileSystem) {
        this._setPath(path);
        this._fileSystem = fileSystem;
        this._id = nextId++;
    }

    // Add "fullPath", "name", "parent", "id", "isFile" and "isDirectory" getters
    Object.defineProperties(FileSystemEntry.prototype, {
        "fullPath": {
            get: function () { return this._path; },
            set: function () { throw new Error("Cannot set fullPath"); }
        },
        "name": {
            get: function () { return this._name; },
            set: function () { throw new Error("Cannot set name"); }
        },
        "parentPath": {
            get: function () { return this._parentPath; },
            set: function () { throw new Error("Cannot set parentPath"); }
        },
        "id": {
            get: function () { return this._id; },
            set: function () { throw new Error("Cannot set id"); }
        },
        "isFile": {
            get: function () { return this._isFile; },
            set: function () { throw new Error("Cannot set isFile"); }
        },
        "isDirectory": {
            get: function () { return this._isDirectory; },
            set: function () { throw new Error("Cannot set isDirectory"); }
        },
        "_impl": {
            get: function () { return this._fileSystem._impl; },
            set: function () { throw new Error("Cannot set _impl"); }
        }
    });

    /**
     * Cached stat object for this file.
     * @type {?FileSystemStats}
     */
    FileSystemEntry.prototype._stat = null;

    /**
     * Parent file system.
     * @type {!FileSystem}
     */
    FileSystemEntry.prototype._fileSystem = null;

    /**
     * The path of this entry.
     * @type {string}
     */
    FileSystemEntry.prototype._path = null;

    /**
     * The name of this entry.
     * @type {string}
     */
    FileSystemEntry.prototype._name = null;

    /**
     * The parent of this entry.
     * @type {string}
     */
    FileSystemEntry.prototype._parentPath = null;

    /**
     * Whether or not the entry is a file
     * @type {boolean}
     */
    FileSystemEntry.prototype._isFile = false;

    /**
     * Whether or not the entry is a directory
     * @type {boolean}
     */
    FileSystemEntry.prototype._isDirectory = false;

    /**
     * Whether or not the entry is watched.
     * @type {boolean}
     */
    FileSystemEntry.prototype._isWatched = false;

    /**
     * Update the path for this entry
     * @private
     * @param {String} newPath
     */
    FileSystemEntry.prototype._setPath = function (newPath) {
        var parts = newPath.split("/");
        if (this.isDirectory) {
            parts.pop(); // Remove the empty string after last trailing "/"
        }
        this._name = parts[parts.length - 1];
        parts.pop(); // Remove name

        if (parts.length > 0) {
            this._parentPath = parts.join("/") + "/";
        } else {
            // root directories have no parent path
            this._parentPath = null;
        }

        this._path = newPath;
    };

    /**
     * Mark this entry as being watched after construction. There is no way to
     * set an entry as being unwatched after construction because entries should
     * be discarded upon being unwatched.
     * @private
     */
    FileSystemEntry.prototype._setWatched = function () {
        this._isWatched = true;
    };

    /**
     * Clear any cached data for this entry
     * @private
     */
    FileSystemEntry.prototype._clearCachedData = function () {
        this._stat = undefined;
    };

    /**
     * Helpful toString for debugging purposes
     */
    FileSystemEntry.prototype.toString = function () {
        return "[" + (this.isDirectory ? "Directory " : "File ") + this._path + "]";
    };

    /**
     * Check to see if the entry exists on disk. Note that there will NOT be an
     * error returned if the file does not exist on the disk; in that case the
     * error parameter will be null and the boolean will be false. The error
     * parameter will only be truthy when an unexpected error was encountered
     * during the test, in which case the state of the entry should be considered
     * unknown.
     *
     * @param {function (?string, boolean)} callback Callback with a FileSystemError
     *      string or a boolean indicating whether or not the file exists.
     */
    FileSystemEntry.prototype.exists = function (callback) {
        if (this._stat) {
            callback(null, true);
            return;
        }

        this._impl.exists(this._path, function (err, exists) {
            if (err) {
                this._clearCachedData();
                callback(err);
                return;
            }

            callback(null, exists);
        }.bind(this));
    };

    /**
     * Returns the stats for the entry.
     *
     * @param {function (?string, FileSystemStats=)} callback Callback with a
     *      FileSystemError string or FileSystemStats object.
     */
    FileSystemEntry.prototype.stat = function (callback) {
        if (this._stat && this._isWatched) {
            callback(null, this._stat);
            return;
        }

        this._impl.stat(this._path, function (err, stat) {
            if (err) {
                this._clearCachedData();
                callback(err);
                return;
            }

            this._stat = stat;

            callback(null, stat);
        }.bind(this));
    };

    /**
     * Rename this entry.
     *
     * @param {string} newFullPath New path & name for this entry.
     * @param {function (?string)=} callback Callback with a single FileSystemError
     *      string parameter.
     */
    FileSystemEntry.prototype.rename = function (newFullPath, callback) {
        callback = callback || function () {};
        this._fileSystem._beginWrite();
        this._impl.rename(this._path, newFullPath, function (err) {
            try {
                if (err) {
                    this._clearCachedData();
                    callback(err);
                    return;
                }

                try {
                    callback(null);  // notify caller
                } finally {
                    // Notify the file system of the name change
                    this._fileSystem._entryRenamed(this._path, newFullPath, this.isDirectory);
                }
            } finally {
                // Unblock external change events
                this._fileSystem._endWrite();
            }
        }.bind(this));
    };

    /**
     * Unlink (delete) this entry. For Directories, this will delete the directory
     * and all of its contents.
     *
     * @param {function (?string)=} callback Callback with a single FileSystemError
     *      string parameter.
     */
    FileSystemEntry.prototype.unlink = function (callback) {
        callback = callback || function () {};

        this._clearCachedData();

        // Block external change events until after the write has finished
        this._fileSystem._beginWrite();

        this._impl.unlink(this._path, function (err) {
            try {
                callback(err);
            } finally {
                this._fileSystem._handleWatchResult(this._parentPath);
                this._fileSystem._index.removeEntry(this);
                this._fileSystem._endWrite();
            }
        }.bind(this));
    };

    /**
     * Move this entry to the trash. If the underlying file system doesn't support move
     * to trash, the item is permanently deleted.
     *
     * @param {function (?string)=} callback Callback with a single FileSystemError
     *      string parameter.
     */
    FileSystemEntry.prototype.moveToTrash = function (callback) {
        if (!this._impl.moveToTrash) {
            this.unlink(callback);
            return;
        }

        callback = callback || function () {};

        this._clearCachedData();

        // Block external change events until after the write has finished
        this._fileSystem._beginWrite();

        this._impl.moveToTrash(this._path, function (err) {
            try {
                callback(err);
            } finally {
                this._fileSystem._handleWatchResult(this._parentPath);
                this._fileSystem._index.removeEntry(this);
                this._fileSystem._endWrite();
            }
        }.bind(this));
    };

    /**
     * Private helper function for FileSystemEntry.visit that requires sanitized options.
     *
     * @private
     * @param {FileSystemStats} stats - the stats for this entry
     * @param {{string: boolean}} visitedPaths - the set of fullPaths that have already been visited
     * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is
     *      applied to descendent FileSystemEntry objects. If the function returns false for
     *      a particular Directory entry, that directory's descendents will not be visited.
     * @param {{maxDepth: number, maxEntriesCounter: {value: number}}} options
     * @param {function(?string)=} callback Callback with single FileSystemError string parameter.
     */
    FileSystemEntry.prototype._visitHelper = function (stats, visitedPaths, visitor, options, callback) {
        var maxDepth = options.maxDepth,
            maxEntriesCounter = options.maxEntriesCounter;

        if (maxEntriesCounter.value-- <= 0 || maxDepth-- < 0) {
            // The outer FileSystemEntry.visit call is responsible for applying
            // the main callback to FileSystemError.TOO_MANY_FILES in this case
            callback(null);
            return;
        }

        if (this.isDirectory) {
            var visitedPath = stats.realPath || this.fullPath;

            if (visitedPaths.hasOwnProperty(visitedPath)) {
                // Link cycle detected
                callback(null);
                return;
            }

            visitedPaths[visitedPath] = true;
        }

        if (!visitor(this) || this.isFile) {
            callback(null);
            return;
        }

        this.getContents(function (err, entries, entriesStats) {
            if (err) {
                callback(err);
                return;
            }

            var counter = entries.length;
            if (counter === 0) {
                callback(null);
                return;
            }

            function helperCallback(err) {
                if (--counter === 0) {
                    callback(null);
                }
            }

            var nextOptions = {
                maxDepth: maxDepth,
                maxEntriesCounter: maxEntriesCounter
            };

            entries.forEach(function (entry, index) {
                var stats = entriesStats[index];
                entry._visitHelper(stats, visitedPaths, visitor, nextOptions, helperCallback);
            });
        }.bind(this));
    };

    /**
     * Visit this entry and its descendents with the supplied visitor function.
     *
     * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is
     *      applied to descendent FileSystemEntry objects. If the function returns false for
     *      a particular Directory entry, that directory's descendents will not be visited.
     * @param {{maxDepth: number=, maxEntries: number=}=} options
     * @param {function(?string)=} callback Callback with single FileSystemError string parameter.
     */
    FileSystemEntry.prototype.visit = function (visitor, options, callback) {
        if (typeof options === "function") {
            callback = options;
            options = {};
        } else if (options === undefined) {
            options = {};
        }

        if (options.maxDepth === undefined) {
            options.maxDepth = VISIT_DEFAULT_MAX_DEPTH;
        }

        if (options.maxEntries === undefined) {
            options.maxEntries = VISIT_DEFAULT_MAX_ENTRIES;
        }

        options.maxEntriesCounter = { value: options.maxEntries };

        this.stat(function (err, stats) {
            if (err) {
                callback(err);
                return;
            }

            this._visitHelper(stats, {}, visitor, options, function (err) {
                if (callback) {
                    if (err) {
                        callback(err);
                        return;
                    }

                    if (options.maxEntriesCounter.value < 0) {
                        callback(FileSystemError.TOO_MANY_ENTRIES);
                        return;
                    }

                    callback(null);
                }
            }.bind(this));
        }.bind(this));
    };

    // Export this class
    module.exports = FileSystemEntry;
});
<|endoftext|>


<commit_before>(function (enyo) {
    var validationWord, formValidated;
    var exceptions, validations, constrains; // Dictionaries
    var runChecker, setElementClasses, shouldValidate, validateForm, findControlByName, validateControl, keyHasError, addEventListener; // Helper functions

    formValidated = false;
    validationWord = "is";

    // Prepublished exceptions
    exceptions = {
        validation: {
            name: "InvalidValidationOperation",
            message: "The validation operation could not be found."
        },
        constrain: {
            name: "InvalidConstrainCheck",
            message: "The constrain check could not be done."
        },
        events: {
            name: "EventsNotSupported",
            message: "The environment is not supporting known event listening API."
        }
    };
    // Validation algorithms
    validations = {
        number: {
            check: function (input) {
                return input === +input;
            },
            error: "should be a number."
        },
        alphanumeric: {
            check: function (input) {
                return (/^\w+$/g).test(input);
            },
            error: "should consist of letters or numbers."
        },
        alphabetical: {
            check: function(input) {
                return (/^[A-Za-z]+$/g).test(input);
            },
            error: "should consist of alphabetical characters only."
        },
        email: {
            check: function (input) {
                return (/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i).test(input); //'
            },
            error: "should be a correctly formatted email address."
        },
        required: {
            check: function (input) {
                return ( !! input && input !== 0) || input === 0;
            },
            error: "should not be empty."
        }
    };
    // Constrain check algorithms
    constrains = {
        min: {
            val: undefined,
            check: function (input) {
                this.error = "should be more than " + this.val + " characters long.";
                return input.length >= this.val;
            },
            error: ""
        },
        max: {
            val: undefined,
            check: function (input) {
                this.error = "should be less than " + this.val + " characters long.";
                return input.length <= this.val;
            },
            error: ""
        },
        gt: {
            val: undefined,
            check: function (input) {
                this.error = "should be greater than " + this.val + ".";
                return input > this.val;
            },
            error: ""
        },
        lt: {
            val: undefined,
            check: function (input) {
                this.error = "should be less than " + this.val + ".";
                return input < this.val;
            },
            error: ""
        },
        eq: {
            val: undefined,
            check: function (input) {
                this.error = "should be equal to " + this.val + ".";
                return input === this.val;
            },
            error: ""
        },
        sameas: {
            val: undefined,
            scope: {},
            check: function (input, inputEl) {
                this.error = "should be same as " + this.val + ".";
                var control = findControlByName.call(this.scope, this.val);
                addEventListener(control.node, "change", this.scope.validate.bind(this.scope, inputEl));
                return input === control.getValue();
            }
        }
    };

    // For some fucked up reason enyo.dispatcher wont work.
    addEventListener = function(inNode, inEventType, inHandler) {
        if (inNode.addEventListener) {
            inNode.addEventListener(inEventType, inHandler, false);
        }
        else if (inNode.attachEvent) {
            inNode.attachEvent('on'+inEventType, inHandler);
        } else {
            throw exceptions.events;
        }
    };

    runChecker = function (checkFn, inputControl) {
        var key = inputControl.getName();
        // Call the `check` function of the validator and the constrains.
        // Put any possible errors in case validation fails.
        if (!checkFn.check(inputControl.getValue(), inputControl)) {
            this.errors[key] = this.errors[key] || [];
            this.errors[key].push(checkFn.error);
        }
    };

    findControlByName = function(name) {
        var foundControl;
        var controls, control, counter;
        controls = this.getControls();
        for(counter = 0; counter<controls.length; counter++ ) {
            if (foundControl) {
                return foundControl;
            }
            control = controls[counter];
            if ( control.name === name ) {
                foundControl = control;
                return foundControl;
            } else {
                foundControl = findControlByName.call(control, name);
            }
        }
        return foundControl;
    };

    keyHasError = function(key) {
        return this.errors.hasOwnProperty(key) && this.errors[key].length > 0;
    };

    setElementClasses = function (inputEl) {
        var inputName = inputEl.getName(),
            errorExists = keyHasError.call(this,inputName),
            parent = inputEl.parent;

        // If it is in a decorator or input decorator (enyo or any class containing string `input-decorator`),
        // the parent class should get the styles.
        if (parent && (parent.hasClass("enyo-tool-decorator") || ~parent.getClassAttribute().indexOf("input-decorator"))) {
            inputEl = parent;
        }
        inputEl.addRemoveClass(this.getErrorClass(),  errorExists);
        inputEl.addRemoveClass(this.getSuccessClass(), !errorExists);
    };

    shouldValidate = function(control) {
        return control.hasOwnProperty(validationWord);
    };

    validateControl = function(control) {
        var prefferedValidations, selectedConstrain;
        prefferedValidations = control[validationWord].toLowerCase().split(" ");
        enyo.forEach(prefferedValidations, function (validationName) {
            if (validations.hasOwnProperty(validationName)) {
                runChecker.apply(this, [validations[validationName], control]);
            } else {
                throw exceptions.validation;
            }
        }, this);

        for (var constrain in constrains) {
            if (control.hasOwnProperty(constrain)) {
                selectedConstrain = constrains[constrain];
                selectedConstrain.val = control[constrain];
                selectedConstrain.scope ? selectedConstrain.scope = this : undefined;
                runChecker.apply(this, [selectedConstrain, control]);
            }
        }

        if (this.getWithClasses() === true) {
            setElementClasses.call(this, control);
        }
    };

    validateForm = function (form) {
        // Recursive check in children controls
        enyo.forEach(form.getControls(), function (control) {
            if (!shouldValidate(control)) {
                validateForm.call(this, control);
            } else {
                validateControl.call(this, control);
            }
        }, this);
        formValidated = true;
    };

    return enyo.kind({
        name: "orthos.Validatable",
        kind: enyo.Control,
        published: {
            errorClass: "orthos-validation-error",
            successClass: "orthos-validation-success",
            withClasses: true,
            live: true
        },
        events: {
            onLiveError: "",
            onLiveSuccess: ""
        },
        handlers: {
            onchange: "_handleChange",
            onkeypress: "_handleKeyPress"
        },
        components: [],
        errors: {},
        statics: {
            /**
             * Adds a custom validation method.
             * @param {String} alias      The alias of the specific validation. e.g. `is:"<alias>"`.
             * @param {(Function|String)} validation The validation function or regexp which will be used to validate the input.
             * @param {String} errorMsg   The error message which  will appear in case of invalid input to the user.
             * @param {Boolean} override  Override existing validation with the same alias.
             */
            addValidation: function(alias, validation, errorMsg, override) {
                var validateFn;
                if (validation instanceof RegExp) {
                    validateFn = function(input) {
                        return validation.test(input);
                    };
                } else if( validation instanceof Function) {
                    validateFn = validation;
                } else {
                    validateFn = function() {
                        return true;
                    };
                    enyo.warn("No validation Function or RegExp passed.");
                }
                if (validations && alias && (!validations[alias] || override) ) {
                    alias = enyo.toLowerCase(alias);
                    validations[alias] = {
                        check: validateFn,
                        error: errorMsg
                    };
                } else {
                    enyo.log("Validation alias already exists.Change alias or set override to true.");
                }
            }
        },
        isValid: function (control) {
            control = control || this;
            var errorKey;
            var controlName;
            if ( control === this ) {
                !formValidated && this.validate(control);
                // Checks if there are any errors in the errors object. If there are, can not be valid.
                for (errorKey in this.errors) {
                    if (keyHasError.call(this, errorKey)) {
                        return false;
                    }
                }
                return true;
            } else {
                // Checks for any error in the errors object based on the control's name as a key.
                // If the specific key has no error then the control is valid.
                return !keyHasError.call(this, control.name);
            }
        },
        /**
         * Returns an array containing all the errors located in the `errors` object.
         * @return array The array with the error objects
         */
        getErrorsArray: function () {
            var errorKey, errorsArray, combinedError;
            errorsArray = [];
            for (errorKey in this.errors) {
                if (keyHasError.call(this,errorKey)) {
                    combinedError = errorKey + " " + this.errors[errorKey][0];
                    if (this.errors[errorKey].length > 1) {
                        for (var i = 1; i < this.errors[errorKey].length; i++) {
                            combinedError = combinedError.substring(0, combinedError.length - 1).replace(" and", ",");
                            combinedError += " and " + this.errors[errorKey][i];
                        }
                    }

                    errorsArray.push(enyo.cap(combinedError));
                }
            }
            return errorsArray;
        },
        /**
         * Returns a formated HTML list of the errors that the `errors` object contains.
         * @return string
         */
        getErrorsHtml: function () {
            var errorsArray, errorsHtml;
            errorsHtml = "";
            errorsArray = this.getErrorsArray();
            if (errorsArray.length !== 0) {
                errorsHtml = "

";
                errorsHtml += errorsArray.join("

");
                errorsHtml += "

";
            }
            return errorsHtml;
        },
        validate: function (control) {
            control = control || this;
            if ( control === this ) {
                // this reffers to the present control which is the container of all inputs - form
                this.errors = {};
                validateForm.call(this, this);
            } else {
                // when specific control is given as an argument
                this.errors = this.errors || {};
                this.errors[control.name] = [];
                validateControl.call(this, control);
            }
        },
        _handleChange: function(inSender, inEvent) {
            var control = inEvent.originator;
            var needsValidation = shouldValidate(control);
            enyo.job.stop("keyPressed"); // In case there is any running keypress timeout running.
            // If form is validated and the control needs validation then mark `formValidated` as `false`
            formValidated = formValidated && !needsValidation;
            if( this.getLive() && needsValidation ){
                this.validate(control);
                if( keyHasError.call(this,control.name) ){
                    this.doLiveError(control);
                } else {
                    this.doLiveSuccess(control);
                }
            }
        },
        _handleKeyPress: function(inSender, inEvent) {
            var args = arguments;
            enyo.job("keyPressed", enyo.bind(this, function(){
                this._handleChange.apply(this, args);
            }), 1000);
        }
    });
})(enyo);
<commit_msg>*Added `optional` validation. *Added clean up of events registered by `addEventListner`, *Validation on `keypress`, *witch of validations<commit_after>(function (enyo) {
    var validationWord;
    var exceptions, validations, constrains; // Dictionaries
    var runChecker, setElementClasses, shouldValidate, validateForm, findControlByName, validateControl, keyHasError, addEventListener; // Helper functions

    validationWord = "is";

    // Prepublished exceptions
    exceptions = {
        validation: {
            name: "InvalidValidationOperation",
            message: "The validation operation could not be found."
        },
        constrain: {
            name: "InvalidConstrainCheck",
            message: "The constrain check could not be done."
        },
        events: {
            name: "EventsNotSupported",
            message: "The environment is not supporting known event listening API."
        }
    };
    // Validation algorithms
    validations = {
        number: {
            check: function (input) {
                return input === +input;
            },
            error: "should be a number."
        },
        alphanumeric: {
            check: function (input) {
                return (/^\w+$/g).test(input);
            },
            error: "should consist of letters or numbers."
        },
        alphabetical: {
            check: function(input) {
                // Exclude booleas as well
                //return (/(?=^(?!(false|true)).)(?=^[A-Za-z]+$)/g).test(input);
                return (/^[A-Za-z]+$/g).test(input);
            },
            error: "should consist of alphabetical characters only."
        },
        email: {
            check: function (input) {
                return (/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i).test(input); //'
            },
            error: "should be a correctly formatted email address."
        },
        required: {
            check: function (input) {
                return ( !! input && input !== 0) || input === 0;
            },
            error: "should not be empty."
        },
        optional: {
            check: function() {
                return true;
            },
            error: ""
        }
    };
    // Constrain check algorithms
    constrains = {
        min: {
            val: undefined,
            check: function (input) {
                this.error = "should be more than " + this.val + " characters long.";
                return input.length >= this.val;
            },
            error: ""
        },
        max: {
            val: undefined,
            check: function (input) {
                this.error = "should be less than " + this.val + " characters long.";
                return input.length <= this.val;
            },
            error: ""
        },
        gt: {
            val: undefined,
            check: function (input) {
                this.error = "should be greater than " + this.val + ".";
                return input > this.val;
            },
            error: ""
        },
        lt: {
            val: undefined,
            check: function (input) {
                this.error = "should be less than " + this.val + ".";
                return input < this.val;
            },
            error: ""
        },
        eq: {
            val: undefined,
            check: function (input) {
                this.error = "should be equal to " + this.val + ".";
                return input === this.val;
            },
            error: ""
        },
        sameas: {
            val: undefined,
            scope: {},
            check: function (input, inputEl) {
                this.error = "should be same as " + this.val + ".";
                var control = findControlByName.call(this.scope, this.val);
                addEventListener(control, "change", this.scope.validate.bind(this.scope, inputEl));
                return input === control.getValue();
            }
        }
    };

    getValidationsArray = function(control) {
        // Change any capital case to lower, remove any spaces at start or end of string and then split it when spaces detected.
        var validationsString, validationsArray;
        validationsString  = control[validationWord].toLowerCase().replace(/(^\s+|\s+$)/g,"");
        if ( validationsString === "" ) {
            return [];
        } else {
            validationsArray = validationsString.split(/\s+/g);
            return validationsArray;
        }
    };

    // For some fucked up reason enyo.dispatcher wont work.
    addEventListener = function(inControl, inEventType, inHandler) {
        var node = inControl.node;
        var destroy = inControl.destroy;
        if (node.addEventListener) {
            node.addEventListener(inEventType, inHandler, false);
            inControl.destroy = function() {
                node.removeEventListener(inEventType, inHandler);
                destroy.apply(inControl, arguments);
            };
        }
        else if (inNode.attachEvent) {
            node.attachEvent('on' + inEventType, inHandler);
            inControl.destroy = function() {
                node.detachEvent('on' + inEventType, inHandler);
                destroy.apply(inControl, arguments);
            };
        } else {
            node["on" + inEventType] = inHandler;
            inControl.destroy = function() {
                node["on" + inEventType] = undefined;
                destroy.apply(inControl, arguments);
            };
        }
    };

    runChecker = function (checkFn, inputControl) {
        var key = inputControl.getName();
        // Call the `check` function of the validator and the constrains.
        // Put any possible errors in case validation fails.
        if (!checkFn.check(inputControl.getValue(), inputControl)) {
            this.errors[key] = this.errors[key] || [];
            this.errors[key].push(checkFn.error);
        }
    };

    findControlByName = function(name) {
        var foundControl;
        var controls, control, counter;
        controls = this.getControls();
        for(counter = 0; counter<controls.length; counter++ ) {
            if (foundControl) {
                return foundControl;
            }
            control = controls[counter];
            if ( control.name === name ) {
                foundControl = control;
                return foundControl;
            } else {
                foundControl = findControlByName.call(control, name);
            }
        }
        return foundControl;
    };

    keyHasError = function(key) {
        return this.errors.hasOwnProperty(key) && this.errors[key].length > 0;
    };

    setElementClasses = function (inputEl) {
        var inputName = inputEl.getName(),
            errorExists = keyHasError.call(this,inputName),
            parent = inputEl.parent;

        // If it is in a decorator or input decorator (enyo or any class containing string `input-decorator`),
        // the parent class should get the styles.
        if (parent && (parent.hasClass("enyo-tool-decorator") || ~parent.getClassAttribute().indexOf("input-decorator"))) {
            inputEl = parent;
        }
        inputEl.addRemoveClass(this.getErrorClass(),  errorExists);
        inputEl.addRemoveClass(this.getSuccessClass(), !errorExists);
    };

    shouldValidate = function(control) {
        return control.hasOwnProperty(validationWord);
    };

    validateControl = function(control) {
        var prefferedValidations, selectedConstrain;
        prefferedValidations = getValidationsArray(control);
        if ( !~prefferedValidations.indexOf("optional") && !~prefferedValidations.indexOf("required") ) {
            prefferedValidations.push("required"); // Make by default required if none is passed
        }
        enyo.forEach(prefferedValidations, function (validationName) {
            if (validations.hasOwnProperty(validationName)) {
                runChecker.apply(this, [validations[validationName], control]);
            } else {
                throw exceptions.validation;
            }
        }, this);

        for (var constrain in constrains) {
            if (control.hasOwnProperty(constrain)) {
                selectedConstrain = constrains[constrain];
                selectedConstrain.val = control[constrain];
                selectedConstrain.scope ? selectedConstrain.scope = this : undefined;
                runChecker.apply(this, [selectedConstrain, control]);
            }
        }

        if (this.getWithClasses() === true) {
            setElementClasses.call(this, control);
        }
    };

    validateForm = function (form) {
        // Recursive check in children controls
        enyo.forEach(form.getControls(), function (control) {
            if (!shouldValidate(control)) {
                validateForm.call(this, control);
            } else {
                validateControl.call(this, control);
            }
        }, this);
        form._validated = true;
    };

    return enyo.kind({
        name: "orthos.Validatable",
        kind: enyo.Control,
        published: {
            errorClass: "orthos-validation-error",
            successClass: "orthos-validation-success",
            withClasses: true,
            live: true
        },
        events: {
            onLiveError: "",
            onLiveSuccess: ""
        },
        handlers: {
            attributesChanged: "_handleChange",
            valueChanged: "_handleChange",
            onchange: "_handleChange",
            onkeypress: "_handleKeyPress"
        },
        components: [],
        errors: {},
        _validated: false,
        create: function() {
            this.inherited(arguments);
            this._validated = false;
        },
        statics: {
            /**
             * Adds a custom validation method.
             * @param {String} alias      The alias of the specific validation. e.g. `is:"<alias>"`.
             * @param {(Function|String)} validation The validation function or regexp which will be used to validate the input.
             * @param {String} errorMsg   The error message which  will appear in case of invalid input to the user.
             * @param {Boolean} override  Override existing validation with the same alias.
             */
            addValidation: function(alias, validation, errorMsg, override) {
                var validateFn;
                if (validation instanceof RegExp) {
                    validateFn = function(input) {
                        return validation.test(input);
                    };
                } else if( validation instanceof Function) {
                    validateFn = validation;
                } else {
                    validateFn = function() {
                        return true;
                    };
                    enyo.warn("No validation Function or RegExp passed.");
                }
                if (validations && alias && (!validations[alias] || override) ) {
                    alias = enyo.toLowerCase(alias);
                    validations[alias] = {
                        check: validateFn,
                        error: errorMsg
                    };
                } else {
                    enyo.log("Validation alias already exists.Change alias or set override to true.");
                }
            }
        },
        /**
         * Checks if a control is valid by looking up in the errors object. If none
         * provided checks for the whole form. If form was never validated before it
         * will try to validate it before checking
         * for its validity
         * @param  {enyo.Instance}  control The control which will be checked
         * @return {Boolean}         False if is invalid, True if valid
         */
        isValid: function (control) {
            control = control || this;
            var errorKey;
            var controlName;
            if ( control === this ) {
                !this._validated && this.validate(control);
                // Checks if there are any errors in the errors object. If there are, can not be valid.
                for (errorKey in this.errors) {
                    if (keyHasError.call(this, errorKey)) {
                        return false;
                    }
                }
                return true;
            } else {
                // Checks for any error in the errors object based on the control's name as a key.
                // If the specific key has no error then the control is valid.
                return !keyHasError.call(this, control.name);
            }
        },
        /**
         * Returns an array containing all the errors located in the `errors` object.
         * @return {Array} The array with the error objects
         */
        getErrorsArray: function () {
            var errorKey, errorsArray, combinedError;
            errorsArray = [];
            for (errorKey in this.errors) {
                if (keyHasError.call(this,errorKey)) {
                    combinedError = errorKey + " " + this.errors[errorKey][0];
                    if (this.errors[errorKey].length > 1) {
                        for (var i = 1; i < this.errors[errorKey].length; i++) {
                            combinedError = combinedError.substring(0, combinedError.length - 1).replace(" and", ",");
                            combinedError += " and " + this.errors[errorKey][i];
                        }
                    }

                    errorsArray.push(enyo.cap(combinedError));
                }
            }
            return errorsArray;
        },
        /**
         * Returns a formated HTML list of the errors that the `errors` object contains.
         * @return {String}
         */
        getErrorsHtml: function () {
            var errorsArray, errorsHtml;
            errorsHtml = "";
            errorsArray = this.getErrorsArray();
            if (errorsArray.length !== 0) {
                errorsHtml = "

";
                errorsHtml += errorsArray.join("

");
                errorsHtml += "

";
            }
            return errorsHtml;
        },
        /**
         * Changes validation methods as stated in `is` property of the control
         * @param  {enyo.Instance} control   An enyo input control
         * @param  {String} inValidation  The new validation
         * @param  {String} outValidation The validation that shall get replaced, if none provided then the new validation will get appended.
         */
        changeContorlValidation: function(control, inValidation, outValidation) {
            var controlValidations = getValidationsArray(control),
                outIndexInValidations = controlValidations.indexOf(outValidation),
                inIndexInValidations = controlValidations.indexOf(inValidation);
            if (enyo.isObject(control) && !!validations[inValidation] && !~inIndexInValidations) {
                if (~outIndexInValidations) {
                    controlValidations[outIndexInValidations] = inValidation;
                } else {
                    controlValidations.push(inValidation);
                }
                control[validationWord] = controlValidations.join(" ");
                this._validated = false;
            }
        },
        /**
         * Validates a specific control if proviced. Otherwise all the input controls in the form.
         * @param  {enyo.Instance} control [description]
         */
        validate: function (control) {
            control = control || this;
            if ( control === this ) {
                // this reffers to the present control which is the container of all inputs - form
                this.errors = {};
                validateForm.call(this, this);
            } else {
                // when specific control is given as an argument
                this.errors = this.errors || {};
                this.errors[control.name] = [];
                validateControl.call(this, control);
            }
        },
        //* Private methods
        _handleChange: function(inSender, inEvent) {
            var control = inEvent.originator;
            var needsValidation = shouldValidate(control);
            enyo.job.stop("keyPressed"); // In case there is any running keypress timeouts.
            // If form is validated and the control needs validation then mark `this._validated` as `false`
            this._validated = this._validated && !needsValidation;
            if( this.getLive() && needsValidation ){
                this.validate(control);
                if( keyHasError.call(this,control.name) ){
                    this.doLiveError(control);
                } else {
                    this.doLiveSuccess(control);
                }
            }
        },
        _handleKeyPress: function(inSender, inEvent) {
            var args = arguments;
            enyo.job("keyPressed", enyo.bind(this, function(){
                this._handleChange.apply(this, args);
            }), 1200);
        }
    });
})(enyo);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.session',
  name: 'Session',

  javaImports: [
    'java.util.Date'
  ],

  properties: [
    {
      class: 'String',
      name: 'id'
    },
    {
      class: 'Long',
      name: 'userId'
    },
    {
      class: 'DateTime',
      name: 'created',
      javaFactory: 'return new Date();'
    },
    {
      class: 'DateTime',
      name: 'lastUsed'
    },
    {
      class: 'Long',
      name: 'uses'
    },
    {
      class: 'DateTime',
      name: 'expiry',
      javaFactory: 'return new Date(System.currentTimeMillis()+30*24*60*60*1000);'
    },
    {
      class: 'String',
      name: 'hostname'
    },
    {
      class: 'Object',
      name: 'context',
      javaType: 'foam.core.X',
      hidden: true,
      transient: true
    }
  ]
});
<commit_msg>Fixed calculation of Session expiry.<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.session',
  name: 'Session',

  javaImports: [
    'java.util.Date'
  ],

  properties: [
    {
      class: 'String',
      name: 'id'
    },
    {
      class: 'Long',
      name: 'userId'
    },
    {
      class: 'DateTime',
      name: 'created',
      javaFactory: 'return new Date();'
    },
    {
      class: 'DateTime',
      name: 'lastUsed'
    },
    {
      class: 'Long',
      name: 'uses'
    },
    {
      class: 'DateTime',
      name: 'expiry',
      javaFactory: 'return new Date(System.currentTimeMillis()+30l*24l*60l*60l*1000l);'
    },
    {
      class: 'String',
      name: 'hostname'
    },
    {
      class: 'Object',
      name: 'context',
      javaType: 'foam.core.X',
      hidden: true,
      transient: true
    }
  ]
});
<|endoftext|>


<commit_before>/*global jQuery, document, gocept */
/*jslint nomen: true, unparam: true, bitwise: true*/

(function ($) {
    "use strict";

    var run, register_click_event;

    run = function (event) {
        $('#form').remove();
        $('#my_collection').remove();
        var button = $(event.currentTarget),
            code_block = button.prev();
        button.after('

');
        eval(code_block.text());
    };

    register_click_event = function (node) {
        var button, clear;
        button = $('<button class="btn btn-neutral coderunner">Run</button>');
        clear = $('');
        node.after(button);
        button.after(clear);
        button.click(run);

    };

    $(document).ready(function () {
        var messages = {
            '0': {title: 'Schedule', 'description': 'Next week is getting important!'},
            '1': {title: 'Meeting update', 'description': 'Things changed for tomorrow'},
        };
        // Mock AJAX calls by gocept.jsform.Form
        gocept.jsform.Form.prototype.reload = function () {
            var self = this;
            setTimeout(function () {
                self.finish_load({"title": "", "description": ""});
            }, 0);
        };
        gocept.jsform.Form.prototype._save = function (id, save_url, save_type, data) {
            data = JSON.parse(data);
            if (data.hasOwnProperty('title')) {
                messages[save_url].title = data.title;
            }
            if (data.hasOwnProperty('description')) {
                messages[save_url].description = data.description;
            }
            var deferred_save = $.Deferred(), apply_response;
            apply_response = function () {
                deferred_save.resolve({status: 'success'});
            };
            setTimeout(apply_response, 0);
            return deferred_save.promise();
        };
        gocept.jsform.ListWidget.prototype.reload = function () {
            var self = this,
                result = [];
            $.each(messages, function (key, value) {
                //id = id.toString();
                result.push({resource: key, data: value});
            });
            self.render(result);
        };

        gocept.jsform.ListWidget.prototype.del_item = function (node) {
            delete messages[node.data('resource')];
            node.remove();
        };

        // Add run button to each JavaScript code block
        $.each(
            $('.highlight-javascript').add($('.highlight-js')),
            function (index, node) {
                // Skip code blocks without ID, i.e. have no reference label
                // (This way we can define which code blocks are executable)
                if (node.id.length) {
                    register_click_event($(node));
                }
            }
        );
    });
}(jQuery));
<commit_msg>Make form examples run again.<commit_after>/*global jQuery, document, gocept */
/*jslint nomen: true, unparam: true, bitwise: true*/

(function ($) {
    "use strict";

    var run, register_click_event;

    run = function (event) {
        $('#form').remove();
        $('#my_collection').remove();
        var button = $(event.currentTarget),
            code_block = button.prev();
        button.after('

');
        button.after('

');
        eval(code_block.text());
    };

    register_click_event = function (node) {
        var button, clear;
        button = $('<button class="btn btn-neutral coderunner">Run</button>');
        clear = $('');
        node.after(button);
        button.after(clear);
        button.click(run);

    };

    $(document).ready(function () {
        var messages = {
            'message/0': {title: 'Schedule', 'description': 'Next week is getting important!'},
            'message/1': {title: 'Meeting update', 'description': 'Things changed for tomorrow'},
        };
        // Mock AJAX calls by gocept.jsform.Form
        gocept.jsform.Form.prototype.reload = function () {
            var self = this;
            setTimeout(function () {
                self.finish_load({"title": "", "description": ""});
            }, 0);
        };
        gocept.jsform.Form.prototype._save = function (id, save_url, save_type, data) {
            data = JSON.parse(data);
            if (data.hasOwnProperty('title')) {
                messages[save_url].title = data.title;
            }
            if (data.hasOwnProperty('description')) {
                messages[save_url].description = data.description;
            }
            var deferred_save = $.Deferred(), apply_response;
            apply_response = function () {
                deferred_save.resolve({status: 'success'});
            };
            setTimeout(apply_response, 0);
            return deferred_save.promise();
        };
        gocept.jsform.ListWidget.prototype.reload = function () {
            var self = this,
                result = [];
            $.each(messages, function (key, value) {
                //id = id.toString();
                result.push({resource: key, data: value});
            });
            self.render(result);
        };

        gocept.jsform.ListWidget.prototype.del_item = function (node) {
            delete messages[node.data('resource')];
            node.remove();
        };

        // Add run button to each JavaScript code block
        $.each(
            $('.highlight-javascript').add($('.highlight-js')),
            function (index, node) {
                // Skip code blocks without ID, i.e. have no reference label
                // (This way we can define which code blocks are executable)
                if (node.id.length) {
                    register_click_event($(node));
                }
            }
        );
    });
}(jQuery));
<|endoftext|>


<commit_before>describe('miq_list_grid.js', function() {
  describe('#miqGridSort', function() {
    it('returns url with no double id', function() {
      ManageIQ.actionUrl = 'show/1000000000015';
      ManageIQ.record.parentClass = 'ems_infra';
      ManageIQ.record.parentId = '1000000000015';
      expect(miqGetSortUrl(1)).toEqual('/ems_infra/show/1000000000015?sortby=1&');
    });
  });

  describe('#checkboxItemId', function() {
    // helper to create a fake jquery object
    var kvToElem = function(name, value) {
      return {
        attr: _.constant(name),
        val: _.constant(value),
      };
    };

    it('correctly parses name=check_#{id} checkboxes', function() {
      expect(checkboxItemId(kvToElem('check_1r2345', '1'))).toEqual('1r2345');
      expect(checkboxItemId(kvToElem('check_12345', '12345'))).toEqual('12345');
      expect(checkboxItemId(kvToElem('check_1', '0'))).toEqual('1');
    });

    it('correctly parses value=#{id} checkboxes', function() {
      expect(checkboxItemId(kvToElem('whatever', '1r2345'))).toEqual('1r2345');
      expect(checkboxItemId(kvToElem('whatever', '12345'))).toEqual('12345');
      expect(checkboxItemId(kvToElem('whatever', '0'))).toEqual('0');
    });
  });

  describe('#miqGridGetCheckedRows', function() {
    beforeEach(function() {
      var html = "";
      html += '
';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_1r2345" value="1" checked>';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_12345" value="1" checked>';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_1" value="1" checked>';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_123" value="1">';
      html += '
';
      setFixtures(html);
    });

    it("picks checked checkboxes from the chosen grid", function() {
      expect(miqGridGetCheckedRows('test_grid_1')).toEqual(['1r2345', '12345', '1']);
    });
  });
});
<commit_msg>Update Jasmine to a released (and Rails 5 compatible) version (transferred from ManageIQ/manageiq@a3af1836d1fab312f537b69484b8514f614ad955)<commit_after>describe('miq_list_grid.js', function() {
  describe('#miqGridSort', function() {
    it('returns url with no double id', function() {
      ManageIQ.actionUrl = 'show/1000000000015';
      ManageIQ.record.parentClass = 'ems_infra';
      ManageIQ.record.parentId = '1000000000015';
      expect(miqGetSortUrl(1)).toMatch(/^\/ems_infra\/show\/1000000000015\?sortby=1&/);
    });
  });

  describe('#checkboxItemId', function() {
    // helper to create a fake jquery object
    var kvToElem = function(name, value) {
      return {
        attr: _.constant(name),
        val: _.constant(value),
      };
    };

    it('correctly parses name=check_#{id} checkboxes', function() {
      expect(checkboxItemId(kvToElem('check_1r2345', '1'))).toEqual('1r2345');
      expect(checkboxItemId(kvToElem('check_12345', '12345'))).toEqual('12345');
      expect(checkboxItemId(kvToElem('check_1', '0'))).toEqual('1');
    });

    it('correctly parses value=#{id} checkboxes', function() {
      expect(checkboxItemId(kvToElem('whatever', '1r2345'))).toEqual('1r2345');
      expect(checkboxItemId(kvToElem('whatever', '12345'))).toEqual('12345');
      expect(checkboxItemId(kvToElem('whatever', '0'))).toEqual('0');
    });
  });

  describe('#miqGridGetCheckedRows', function() {
    beforeEach(function() {
      var html = "";
      html += '
';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_1r2345" value="1" checked>';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_12345" value="1" checked>';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_1" value="1" checked>';
      html += '  <input type="checkbox" class="list-grid-checkbox" name="check_123" value="1">';
      html += '
';
      setFixtures(html);
    });

    it("picks checked checkboxes from the chosen grid", function() {
      expect(miqGridGetCheckedRows('test_grid_1')).toEqual(['1r2345', '12345', '1']);
    });
  });
});
<|endoftext|>


<commit_before>import clone from 'clone'

module.exports = filters

function filters(options) {
  const keys = splitKeys(options)
  const negativeKeys = keys
    .filter(key => key.startsWith('-'))
    .map(key => key.replace('-', ''))
  const positiveKeys = keys.filter(key => !key.startsWith('-'))

  console.log(keys, 'positive', positiveKeys, 'negative', negativeKeys)

  const obj = clone(this)
  return obj
}

function splitKeys(options='') {
  const keys = Array.isArray(options)
    ? options.join(' ')
    : options.toString()

  return keys
    .trim()
    .replace(/\s{2,}/g, ' ')
    .split(' ')
    .filter(key => key)
}
<commit_msg>simple reorder vars negative and positive keys<commit_after>import clone from 'clone'

module.exports = filters

function filters(options) {
  const keys = splitKeys(options)
  const positiveKeys = keys.filter(key => !key.startsWith('-'))
  const negativeKeys = keys
    .filter(key => key.startsWith('-'))
    .map(key => key.replace('-', ''))

  console.log(keys, 'positive', positiveKeys, 'negative', negativeKeys)

  const obj = clone(this)
  return obj
}

function splitKeys(options='') {
  const keys = Array.isArray(options)
    ? options.join(' ')
    : options.toString()

  return keys
    .trim()
    .replace(/\s{2,}/g, ' ')
    .split(' ')
    .filter(key => key)
}
<|endoftext|>


<commit_before>hqDefine('app_manager/js/summary/form_summary', function() {
    var assertProperties = hqImport("hqwebapp/js/assert_properties"),
        initialPageData = hqImport("hqwebapp/js/initial_page_data"),
        models = hqImport("app_manager/js/summary/models"),
        utils = hqImport('app_manager/js/summary/utils');

    var formSummaryModel = function(options) {
        var self = models.contentModel(_.extend(options, {
            query_label: gettext("Filter questions"),
            onQuery: function(query) {
                var match = function(needle, haystack) {
                    return !needle || haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
                };
                _.each(self.modules, function(module) {
                    var moduleIsVisible = match(query, self.translate(module.name));
                    _.each(module.forms, function(form) {
                        var formIsVisible = match(query, self.translate(form.name));
                        _.each(form.questions, function(question) {
                            var questionIsVisible = match(query, question.value + self.translateQuestion(question));
                            questionIsVisible = questionIsVisible || _.find(question.options, function(option) {
                                return match(query, option.value + self.translateQuestion(option));
                            });
                            question.matchesQuery(questionIsVisible);
                            formIsVisible = formIsVisible || questionIsVisible;
                        });
                        form.matchesQuery(formIsVisible);
                        moduleIsVisible = moduleIsVisible || formIsVisible;
                    });
                    module.matchesQuery(moduleIsVisible);
                });
            },
            onSelectMenuItem: function(selectedId) {
                _.each(self.modules, function(module) {
                    module.isSelected(!selectedId || selectedId === module.id || _.find(module.forms, function(f) { return selectedId === f.id; }));
                    _.each(module.forms, function(form) {
                        form.isSelected(!selectedId || selectedId === form.id || selectedId === module.id);
                    });
                });
            },
        }));

        assertProperties.assertRequired(options, ['errors', 'modules']);
        self.errors = options.errors;
        self.modules = _.map(options.modules, moduleModel);

        self.showCalculations = ko.observable(false);
        self.toggleCalculations = function() {
            self.showCalculations(!self.showCalculations());
        };

        self.showRelevance = ko.observable(false);
        self.toggleRelevance = function() {
            self.showRelevance(!self.showRelevance());
        };

        self.showConstraints = ko.observable(false);
        self.toggleConstraints = function() {
            self.showConstraints(!self.showConstraints());
        };

        self.showComments = ko.observable(false);
        self.toggleComments = function() {
            self.showComments(!self.showComments());
        };

        self.showDefaultValues = ko.observable(false);
        self.toggleDefaultValues = function() {
            self.showDefaultValues(!self.showDefaultValues());
        };

        return self;
    };

    var moduleModel = function(module) {
        var self = models.contentItemModel(module);

        self.url = hqImport("hqwebapp/js/initial_page_data").reverse("view_module", self.id);
        self.icon = utils.moduleIcon(self) + ' hq-icon';
        self.forms = _.map(self.forms, formModel);

        return self;
    };

    var formModel = function(form) {
        var self = models.contentItemModel(form);

        self.url = hqImport("hqwebapp/js/initial_page_data").reverse("form_source", self.id);
        self.icon = utils.formIcon(self) + ' hq-icon';
        self.questions = _.map(self.questions, function(question) {
            return models.contentItemModel(_.defaults({
                options: [],
            }, question));
        });

        return self;
    };

    $(function() {
        var lang = initialPageData.get('lang'),
            langs = initialPageData.get('langs');

        var formSummaryMenu = models.menuModel({
            items: _.map(initialPageData.get("modules"), function(module) {
                return models.menuItemModel({
                    id: module.id,
                    name: utils.translateName(module.name, lang, langs),
                    icon: utils.moduleIcon(module),
                    has_errors: false,
                    subitems: _.map(module.forms, function(form) {
                        return models.menuItemModel({
                            id: form.id,
                            name: utils.translateName(form.name, lang, langs),
                            icon: utils.formIcon(form),
                        });
                    }),
                });
            }),
            viewAllItems: gettext("View All Forms"),
        });

        var formSummaryContent = formSummaryModel({
            errors: initialPageData.get("errors"),
            form_name_map: initialPageData.get("form_name_map"),
            lang: lang,
            langs: langs,
            modules: initialPageData.get("modules"),
            read_only: initialPageData.get("read_only"),
        });

        models.initSummary(formSummaryMenu, formSummaryContent);
    });
});
<commit_msg>Flip _.default args<commit_after>hqDefine('app_manager/js/summary/form_summary', function() {
    var assertProperties = hqImport("hqwebapp/js/assert_properties"),
        initialPageData = hqImport("hqwebapp/js/initial_page_data"),
        models = hqImport("app_manager/js/summary/models"),
        utils = hqImport('app_manager/js/summary/utils');

    var formSummaryModel = function(options) {
        var self = models.contentModel(_.extend(options, {
            query_label: gettext("Filter questions"),
            onQuery: function(query) {
                var match = function(needle, haystack) {
                    return !needle || haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
                };
                _.each(self.modules, function(module) {
                    var moduleIsVisible = match(query, self.translate(module.name));
                    _.each(module.forms, function(form) {
                        var formIsVisible = match(query, self.translate(form.name));
                        _.each(form.questions, function(question) {
                            var questionIsVisible = match(query, question.value + self.translateQuestion(question));
                            questionIsVisible = questionIsVisible || _.find(question.options, function(option) {
                                return match(query, option.value + self.translateQuestion(option));
                            });
                            question.matchesQuery(questionIsVisible);
                            formIsVisible = formIsVisible || questionIsVisible;
                        });
                        form.matchesQuery(formIsVisible);
                        moduleIsVisible = moduleIsVisible || formIsVisible;
                    });
                    module.matchesQuery(moduleIsVisible);
                });
            },
            onSelectMenuItem: function(selectedId) {
                _.each(self.modules, function(module) {
                    module.isSelected(!selectedId || selectedId === module.id || _.find(module.forms, function(f) { return selectedId === f.id; }));
                    _.each(module.forms, function(form) {
                        form.isSelected(!selectedId || selectedId === form.id || selectedId === module.id);
                    });
                });
            },
        }));

        assertProperties.assertRequired(options, ['errors', 'modules']);
        self.errors = options.errors;
        self.modules = _.map(options.modules, moduleModel);

        self.showCalculations = ko.observable(false);
        self.toggleCalculations = function() {
            self.showCalculations(!self.showCalculations());
        };

        self.showRelevance = ko.observable(false);
        self.toggleRelevance = function() {
            self.showRelevance(!self.showRelevance());
        };

        self.showConstraints = ko.observable(false);
        self.toggleConstraints = function() {
            self.showConstraints(!self.showConstraints());
        };

        self.showComments = ko.observable(false);
        self.toggleComments = function() {
            self.showComments(!self.showComments());
        };

        self.showDefaultValues = ko.observable(false);
        self.toggleDefaultValues = function() {
            self.showDefaultValues(!self.showDefaultValues());
        };

        return self;
    };

    var moduleModel = function(module) {
        var self = models.contentItemModel(module);

        self.url = hqImport("hqwebapp/js/initial_page_data").reverse("view_module", self.id);
        self.icon = utils.moduleIcon(self) + ' hq-icon';
        self.forms = _.map(self.forms, formModel);

        return self;
    };

    var formModel = function(form) {
        var self = models.contentItemModel(form);

        self.url = hqImport("hqwebapp/js/initial_page_data").reverse("form_source", self.id);
        self.icon = utils.formIcon(self) + ' hq-icon';
        self.questions = _.map(self.questions, function(question) {
            return models.contentItemModel(_.defaults(question, {
                options: [],
            }));
        });

        return self;
    };

    $(function() {
        var lang = initialPageData.get('lang'),
            langs = initialPageData.get('langs');

        var formSummaryMenu = models.menuModel({
            items: _.map(initialPageData.get("modules"), function(module) {
                return models.menuItemModel({
                    id: module.id,
                    name: utils.translateName(module.name, lang, langs),
                    icon: utils.moduleIcon(module),
                    has_errors: false,
                    subitems: _.map(module.forms, function(form) {
                        return models.menuItemModel({
                            id: form.id,
                            name: utils.translateName(form.name, lang, langs),
                            icon: utils.formIcon(form),
                        });
                    }),
                });
            }),
            viewAllItems: gettext("View All Forms"),
        });

        var formSummaryContent = formSummaryModel({
            errors: initialPageData.get("errors"),
            form_name_map: initialPageData.get("form_name_map"),
            lang: lang,
            langs: langs,
            modules: initialPageData.get("modules"),
            read_only: initialPageData.get("read_only"),
        });

        models.initSummary(formSummaryMenu, formSummaryContent);
    });
});
<|endoftext|>


<commit_before>function api_get_children(loc_uuid, callback) {
    var params = (loc_uuid ? {parent_id: loc_uuid} : {});
    $('#loc_ajax').show().removeClass('hide');
    $.getJSON(LOAD_LOCS_URL, params, function(allData) {
        $('#loc_ajax').hide().addClass('hide');
        callback(allData.objects);
    });
}

function LocationSelectViewModel(hierarchy, default_caption, auto_drill, loc_filter, func, show_location_filter) {
    var model = this;

    this.default_caption = default_caption || 'All';
    this.auto_drill = (_.isBoolean(auto_drill) ? auto_drill : true);
    this.loc_filter = loc_filter || function(loc) { return true; };
    this.func = typeof func !== 'undefined' ? func : LocationModel;
    this.show_location_filter = ko.observable((typeof show_location_filter !== 'undefined') ? show_location_filter : 'y');

    this.root = ko.observable();
    this.selected_path = ko.observableArray();

    this.location_types = $.map(hierarchy, function(e) {
        return {type: e[0], allowed_parents: e[1]};
    });

    this.show_location_filter_bool = ko.computed(function() {
        return model.show_location_filter() === 'y';
    });

    // currently selected location in the tree (or null)
    this.selected_location = ko.computed(function() {
        for (var i = this.selected_path().length - 1; i >= 0; i--) {
            var loc = this.selected_path()[i];
            if (loc.selected_is_valid()) {
                return loc.selected_child();
            }
        }
        return null;
    }, this);
    // uuid of currently selected location (or null)
    this.selected_locid = ko.computed(function() {
        if(!model.show_location_filter_bool()) {
            return null;
        }
        return this.selected_location() ? this.selected_location().uuid() : null;
    }, this);

    // add a new level of drill-down to the tree
    this.path_push = function(loc) {
        if (this.selected_path().length != this.location_types.length && this.selected_path.indexOf(loc) === -1) {
            this.selected_path.push(loc);
            if (this.auto_drill && loc.num_children() == 1) {
                loc.selected_child(loc.get_child(0));
            }
        }
    };

    // search for a location within the tree by uuid; return path to location if found
    this.find_loc = function(uuid, loc) {
        loc = loc || this.root();

        if (loc.uuid() == uuid) {
            return [loc];
        } else {
            var path = null;
            $.each(loc.children(), function(i, e) {
                var subpath = model.find_loc(uuid, e);
                if (subpath) {
                    path = subpath;
                    path.splice(0, 0, loc);
                    return false;
                }
            });
            return path;
        }
    };

    // load location hierarchy and set initial path
    this.load = function(locs, selected) {
        this.root(new model.func({name: '_root', children: locs}, this));
        this.path_push(this.root());

        if (selected) {
            // this relies on the hierarchy of the selected location being pre-populated
            // in the initial locations set from the server (i.e., no location of the
            // pre-selected location's lineage is loaded asynchronously
            var sel_path = this.find_loc(selected);
            if (sel_path) {
                for (var i = 1; i < sel_path.length; i++) {
                    sel_path[i - 1].selected_child(sel_path[i]);
                }
            }
        }
    };
}

function LocationModel(data, root, depth, func, withAllOption) {
    var loc = this;

    this.name = ko.observable();
    this.type = ko.observable();
    this.uuid = ko.observable();
    this.can_edit = ko.observable();
    this.children = ko.observableArray();
    this.depth = depth || 0;
    this.children_loaded = false;
    this.func = typeof func !== 'undefined' ? func : LocationModel;
    this.withAllOption = typeof withAllOption !== 'undefined' ? withAllOption : true;

    this.display_name = ko.computed(function() {
        return this.name() == '_all' ? root.default_caption : this.name();
    }, this);

    this.selected_child = ko.observable();
    // when a location is selected, update the drill-down tree
    this.selected_child.subscribe(function(val) {
        if (!val) {
            return;
        }

        var removed = root.selected_path.splice(val.depth, 99);
        $.each(removed, function(i, e) {
            // reset so dropdown for loc will default to 'all' if shown again
            e.selected_child(null);
        });

        var post_children_loaded = function(parent) {
            if (parent.num_children()) {
                root.path_push(parent);
            }
        };

        if (!!val.uuid() && !val.children_loaded) {
            val.load_children_async(post_children_loaded);
        } else {
            post_children_loaded(val);
        }
    }, this);
    this.selected_is_valid = ko.computed(function() {
        return this.selected_child() && this.selected_child().name() != '_all';
    }, this);

    // helpers to account for the 'all' meta-entry
    this.num_children = ko.computed(function() {
        var length = this.children().length;
        if (this.withAllOption && length !== 0) {
            length -= 1;
        }
        return length;
    }, this);
    this.get_child = function(i) {
        return this.children()[i + 1];
    };

    this.load = function(data) {
        this.name(data.name);
        this.type(data.location_type);
        this.uuid(data.uuid);
        this.can_edit(_.isBoolean(data.can_edit) ? data.can_edit : true);
        if (!!data.children) {
            this.set_children(data.children);
        }
    };

    this.set_children = function(data) {
        var children = [];
        if (data) {
            children = _.sortBy(data, function(e) { return e.name; });

            //'all choices' meta-entry; annoying that we have to stuff this in
            //the children list, but all my attempts to make computed observables
            //based of children() caused infinite loops.
            if(loc.withAllOption || (!loc.withAllOption && loc.depth > REQUIRED))
                children.splice(0, 0, {name: '_all'});
        }
        this.children($.map(children, function(e) {
            var child = new loc.func(e, root, loc.depth + 1);
            return (child.filter() ? child : null);
        }));
        this.children_loaded = true;
    };

    this.load_children_async = function(callback) {
        api_get_children(this.uuid(), function(resp) {
            loc.set_children(resp);
            callback(loc);
        });
    };

    //warning: duplicate code with location_tree.async.js
    this.allowed_child_types = ko.computed(function() {
        var loc = this;
        var types = [];
        $.each(root.location_types, function(i, loc_type) {
            $.each(loc_type.allowed_parents, function(i, parent_type) {
                if (loc.type() == parent_type) {
                    types.push(loc_type.type);
                }
            });
        });
        return types;
    }, this);

    this.can_have_children = ko.computed(function() {
        return (this.allowed_child_types().length > 0);
    }, this);

    this.filter = function() {
        return this.name() == '_all' || root.loc_filter(this);
    };

    this.can_edit_children = function() {
        // Are there more than one editable options?
        return this.children().filter(function(child) {
            return (child.name() !== '_all' && child.can_edit());
        }).length > 1;
    };

    this.load(data);
}
<commit_msg>Fix some undefineds and equality checks [ci skip]<commit_after>/* globals LOAD_LOCS_URL, REQUIRED */

function api_get_children(loc_uuid, callback) {
    var params = (loc_uuid ? {parent_id: loc_uuid} : {});
    $('#loc_ajax').show().removeClass('hide');
    $.getJSON(LOAD_LOCS_URL, params, function(allData) {
        $('#loc_ajax').hide().addClass('hide');
        callback(allData.objects);
    });
}

function LocationSelectViewModel(hierarchy, default_caption, auto_drill, loc_filter, func, show_location_filter) {
    var model = this;

    this.default_caption = default_caption || 'All';
    this.auto_drill = (_.isBoolean(auto_drill) ? auto_drill : true);
    this.loc_filter = loc_filter || function(loc) { return true; };
    this.func = typeof func !== 'undefined' ? func : LocationModel;
    this.show_location_filter = ko.observable((typeof show_location_filter !== 'undefined') ? show_location_filter : 'y');

    this.root = ko.observable();
    this.selected_path = ko.observableArray();

    this.location_types = $.map(hierarchy, function(e) {
        return {type: e[0], allowed_parents: e[1]};
    });

    this.show_location_filter_bool = ko.computed(function() {
        return model.show_location_filter() === 'y';
    });

    // currently selected location in the tree (or null)
    this.selected_location = ko.computed(function() {
        for (var i = this.selected_path().length - 1; i >= 0; i--) {
            var loc = this.selected_path()[i];
            if (loc.selected_is_valid()) {
                return loc.selected_child();
            }
        }
        return null;
    }, this);
    // uuid of currently selected location (or null)
    this.selected_locid = ko.computed(function() {
        if(!model.show_location_filter_bool()) {
            return null;
        }
        return this.selected_location() ? this.selected_location().uuid() : null;
    }, this);

    // add a new level of drill-down to the tree
    this.path_push = function(loc) {
        if (this.selected_path().length !== this.location_types.length && this.selected_path.indexOf(loc) === -1) {
            this.selected_path.push(loc);
            if (this.auto_drill && loc.num_children() === 1) {
                loc.selected_child(loc.get_child(0));
            }
        }
    };

    // search for a location within the tree by uuid; return path to location if found
    this.find_loc = function(uuid, loc) {
        loc = loc || this.root();

        if (loc.uuid() === uuid) {
            return [loc];
        } else {
            var path = null;
            $.each(loc.children(), function(i, e) {
                var subpath = model.find_loc(uuid, e);
                if (subpath) {
                    path = subpath;
                    path.splice(0, 0, loc);
                    return false;
                }
            });
            return path;
        }
    };

    // load location hierarchy and set initial path
    this.load = function(locs, selected) {
        this.root(new model.func({name: '_root', children: locs}, this));
        this.path_push(this.root());

        if (selected) {
            // this relies on the hierarchy of the selected location being pre-populated
            // in the initial locations set from the server (i.e., no location of the
            // pre-selected location's lineage is loaded asynchronously
            var sel_path = this.find_loc(selected);
            if (sel_path) {
                for (var i = 1; i < sel_path.length; i++) {
                    sel_path[i - 1].selected_child(sel_path[i]);
                }
            }
        }
    };
}

function LocationModel(data, root, depth, func, withAllOption) {
    var loc = this;

    this.name = ko.observable();
    this.type = ko.observable();
    this.uuid = ko.observable();
    this.can_edit = ko.observable();
    this.children = ko.observableArray();
    this.depth = depth || 0;
    this.children_loaded = false;
    this.func = typeof func !== 'undefined' ? func : LocationModel;
    this.withAllOption = typeof withAllOption !== 'undefined' ? withAllOption : true;

    this.display_name = ko.computed(function() {
        return this.name() === '_all' ? root.default_caption : this.name();
    }, this);

    this.selected_child = ko.observable();
    // when a location is selected, update the drill-down tree
    this.selected_child.subscribe(function(val) {
        if (!val) {
            return;
        }

        var removed = root.selected_path.splice(val.depth, 99);
        $.each(removed, function(i, e) {
            // reset so dropdown for loc will default to 'all' if shown again
            e.selected_child(null);
        });

        var post_children_loaded = function(parent) {
            if (parent.num_children()) {
                root.path_push(parent);
            }
        };

        if (!!val.uuid() && !val.children_loaded) {
            val.load_children_async(post_children_loaded);
        } else {
            post_children_loaded(val);
        }
    }, this);
    this.selected_is_valid = ko.computed(function() {
        return this.selected_child() && this.selected_child().name() !== '_all';
    }, this);

    // helpers to account for the 'all' meta-entry
    this.num_children = ko.computed(function() {
        var length = this.children().length;
        if (this.withAllOption && length !== 0) {
            length -= 1;
        }
        return length;
    }, this);
    this.get_child = function(i) {
        return this.children()[i + 1];
    };

    this.load = function(data) {
        this.name(data.name);
        this.type(data.location_type);
        this.uuid(data.uuid);
        this.can_edit(_.isBoolean(data.can_edit) ? data.can_edit : true);
        if (data.children) {
            this.set_children(data.children);
        }
    };

    this.set_children = function(data) {
        var children = [];
        if (data) {
            children = _.sortBy(data, function(e) { return e.name; });

            //'all choices' meta-entry; annoying that we have to stuff this in
            //the children list, but all my attempts to make computed observables
            //based of children() caused infinite loops.
            if(loc.withAllOption || (!loc.withAllOption && loc.depth > REQUIRED))
                children.splice(0, 0, {name: '_all'});
        }
        this.children($.map(children, function(e) {
            var child = new loc.func(e, root, loc.depth + 1);
            return (child.filter() ? child : null);
        }));
        this.children_loaded = true;
    };

    this.load_children_async = function(callback) {
        api_get_children(this.uuid(), function(resp) {
            loc.set_children(resp);
            callback(loc);
        });
    };

    //warning: duplicate code with location_tree.async.js
    this.allowed_child_types = ko.computed(function() {
        var loc = this;
        var types = [];
        $.each(root.location_types, function(i, loc_type) {
            $.each(loc_type.allowed_parents, function(i, parent_type) {
                if (loc.type() === parent_type) {
                    types.push(loc_type.type);
                }
            });
        });
        return types;
    }, this);

    this.can_have_children = ko.computed(function() {
        return (this.allowed_child_types().length > 0);
    }, this);

    this.filter = function() {
        return this.name() === '_all' || root.loc_filter(this);
    };

    this.can_edit_children = function() {
        // Are there more than one editable options?
        return this.children().filter(function(child) {
            return (child.name() !== '_all' && child.can_edit());
        }).length > 1;
    };

    this.load(data);
}
<|endoftext|>