<commit_before>/* The MIT License (MIT)
 *
 * Copyright (c) 2014 Philipp Naderer
 * Copyright (c) 2014 Robert Gaggl
 *
 * 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.
 */

var strings = require("ringo/utils/strings");

/**
 * Creates a new Validator instance
 * @class Instances of this object provide the functionality to
 * validate values and collect messages explaining the validation
 * failure.
 * @returns {Validator} A newly created validator instance
 * @example
 *
 * var validator = new Validator({
 *   "email": "foo.bar@example.com"
 * });
 *
 * // validate a value
 * validator.validate("email").
 *          isDefined("Email address is missing!").
 *          isEmail("Invalid email address provided");
 *
 * // returns true if a validation failed
 * validator.hasErrors();
 *
 * // returns an array with all error messages
 * validator.errorMessages();
 *
 * // returns an object suitable for use as rendering context
 * validator.getTemplateContext();
 */
var Validator = exports.Validator = function(objectToValidate) {
   /** The object to validate */
   this.obj = objectToValidate;

   /** @ignore */
   this.validations = {};

   /** @ignore */
   this.addValidation = function(key, stopOnFail) {
      return (this.validations[key] = new Validation(key, this.obj[key], stopOnFail));
   };
};

/**
 * Main validation entry point. The result of this method contains
 * various validation methods that can be chained. Note that this
 * validation object stops validating at the first fail.
 * @param {String} name The name of the value
 * @returns {Validation} A Validation instance
 * @see Validator.prototype.validateAll
 */
Validator.prototype.validate = function(key) {
   return this.addValidation(key, true);
};

/**
 * Main validation entry point. The result of this method contains
 * various validation methods that can be chained. Note that this
 * validation object does not stop at the first fail but continues
 * to validate all validation methods.
 * @param {String} name The name of the value
 * @param {Object} value The value to validate
 * @returns {Validation} A Validation instance
 * @see Validator.prototype.validate
 */
Validator.prototype.validateAll = function(key) {
   return this.addValidation(key, false);
};

/**
 * Returns true if any of the validations of this validator failed
 * @param {String} name Optional name of a single value to check
 * @returns {Boolean} True if any (or the specified value) validation failed
 */
Validator.prototype.hasErrors = function(name) {
    if (name != undefined) {
        return !this.validations.hasOwnProperty(name) || !this.validations[name].isValid;
    }
    return true !== Object.keys(this.validations).every(function(key) {
        return this[key].isValid;
    }, this.validations);
};

/**
 * Returns the validation failure messages.
 * @param {String} key Optional key to limit the failure messages to a single validation
 * @returns {Array} An array containing all failure messages
 */
Validator.prototype.errorMessages = function(key) {
   if (key != undefined) {
      return this.hasErrors(key) ? this.validations[key].messages : [];
   }

   var messages = [];
   for each (let validation in this.validations) {
      if (!validation.isValid) {
         messages = messages.concat(validation.messages);
      }
   }
   return messages;
};

/**
 * Returns an object suitable for use as template rendering context.
 * This object contains all values stored by their name, an
 * object named `errors` containing the validation failure messages
 * by value name, and a boolean `hasErrors`.
 * @returns {Object} The template context object
 */
Validator.prototype.getTemplateContext = function() {
    var result = {
        "hasErrors": this.hasErrors(),
        "errors": {}
    };
    for each (let validation in this.validations) {
        result[validation.name] = validation.value;
        if (!validation.isValid) {
            result.errors[validation.name] = validation.messages;
        }
    }
    return result;
};

var VoidValidation = function(value) {
   this.value = value;
};

/**
 * @class Instances of this class represent a single validation
 * and provide various methods for validating its value.
 * @param {Object} validation An object containing the name of the property,
 *        the value, a validity state and an array of messages.
 * @param {Boolean} stopOnFail If false the all validation
 * method calls in a chain will be executed (optional, defaults to true)
 * @returns {Validation} A newly created validation instance
 * @constructor
 */
var Validation = exports.Validation = function(key, value, stopOnFail) {

   Object.defineProperties(this, {
      "key": { "value": key },
      "value": { "value": value },
      "stopOnFail": { "value": stopOnFail === true },
      "isValid": { "value": true, writable: true },
      "messages": { "value": [], writable: true }
   });

   return this;
};

/** @ignore */
var checkResult = function(validationObj, passes, message) {
   if (!passes) {
      validationObj.isValid = false;
      validationObj.messages.push(message);

      if (validationObj.stopOnFail === true) {
         return new VoidValidation(validationObj.value);
      }
   }

   return validationObj;
};

/**
 * Passes if the value is alphabetic.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isAlpha = function(message) {
   return checkResult(this, strings.isAlpha(this.value), message);
};

/**
 * Passes if the value contains only letters of the alphabet and numbers.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isAlphanumeric = function(message) {
   return checkResult(this, strings.isAlphanumeric(this.value), message);
};

/**
 * Passes if the value is a valid date format pattern.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isDateFormat = function(message) {
   return checkResult(this, strings.isDateFormat(this.value), message);
};

/**
 * Passes if the value is a valid URL.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isUrl = function(message) {
   return checkResult(this, strings.isUrl(this.value), message);
};

/**
 * Passes if the value is a valid email address.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isEmail = function(message) {
   return checkResult(this, strings.isEmail(this.value), message);
};

/**
 * Passes if the value contains no characters
 * that are forbidden in file names.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isFileName = function(message) {
   return checkResult(this, strings.isFileName(this.value), message);
};

/**
 * Passes if the value is a valid color value in hexadecimal format.
 * It may also contain # as first character.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isHexColor = function(message) {
   return checkResult(this, strings.isHexColor(this.value), message);
};

/**
 * Passes if the value contains only the characters [0-9].
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isNumeric = function(message) {
   return checkResult(this, strings.isNumeric(this.value), message);
};

/**
 * Passes if the value is a valid integer literal.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isInt = function(message) {
   return checkResult(this, strings.isInt(this.value), message);
};

/**
 * Passes if the value is a valid floating point literal.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isFloat = function(message) {
   return checkResult(this, strings.isFloat(this.value), message);
};

/**
 * Passes if the value is a valid floating point or integer literal.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isNumber = function(message) {
   return checkResult(this, (strings.isFloat(this.value) || strings.isInt(this.value)), message);
};

/**
 * Passes if the value's length is not shorter than minLength.
 * @param {Number} minLength The minimal length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.minLength = function(minLength, message) {
   return checkResult(this, (this.value.length >= minLength), message);
};

/**
 * Passes if the value's length is not longer than maxLength.
 * @param {Number} maxLength The maximum length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.maxLength = function(maxLength, message) {
   return checkResult(this, (this.value.length <= maxLength), message);
};

/**
 * Passes if the value's length is between the minLength and maxLength.
 * @param {Number} minLength The minimal length of value
 * @param {Number} maxLength The maximum length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.lengthBetween = function(minLength, maxLength, message) {
   return checkResult(this, (this.value.length >= minLength && this.value.length <= maxLength), message);
};

/**
 * Passes if the value has the given length.
 * @param {Number} length The required length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.hasLength = function(length, message) {
   return checkResult(this, (this.value.length === length), message);
};

/**
 * Passes if the value is equal to the given object.
 * @param {Object} obj The object to compare
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.equal = function(obj, message) {
   return checkResult(this, (this.value == obj), message);
};

/**
 * Passes if the value is strict equal to the given object.
 * @param {Object} obj The object to compare
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.strictEqual = function(obj, message) {
   return checkResult(this, (this.value === obj), message);
};

/**
 * Passes if the value is true.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isTrue = function(message) {
   return checkResult(this, this.value, message);
};

/**
 * Passes if the value is false.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isFalse = function(message) {
   return checkResult(this, !this.value, message);
};

/**
 * Passes if the value is not undefined.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isDefined = function(message) {
   return checkResult(this, this.value !== undefined, message);
};

/**
 * Passes if the value is not null.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.notNull = function(message) {
   return checkResult(this, this.value != null, message);
};

/**
 * Passes if the value is strict not null.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.strictNotNull = function(message) {
   return checkResult(this, this.value !== null, message);
};

/**
 * Passes if the value matched the given regular expression.
 * @param {RegExp} regex The regular expression to match
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.matches = function(regex, message) {
   return checkResult(this, regex.test(this.value), message);
};

/**
 * Passes if the given function returns true.
 * The function receives the value as first argument.
 * @param {Function} func The validation function
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.passes = function(func, message) {
   if (typeof func !== "function") {
      throw "Validator function argument is not a function!";
   }

   return checkResult(this, func(this.value) === true, message);
};

/*** Sanitizers ***/

/**
 * Converts the value to a Date object, if possible.
 * @returns {Date} The value as Date, or null if parsing failed
 */
Validation.prototype.toDate = function() {
   var date = Date.parse(this.value);
   return (!isNaN(date) ? new Date(date) : null);
};

/**
 * Converts the value to a floating point number.
 * @returns {Number} The value as floating point number, or NaN if if parsing failed
 */
Validation.prototype.toFloat = function () {
   if (typeof this.value === "string" && !strings.isFloat(this.value)) {
      return NaN;
   }

   return parseFloat(this.value);
};

/**
 * Converts the value to an integer number.
 * @returns {Number} The value as integer number, or NaN if if parsing failed
 */
Validation.prototype.toInt = function(radix) {
   if (typeof this.value === "string" && !strings.isInt(this.value)) {
      return NaN;
   }

   return parseInt(this.value, radix || 10);
};

/**
 * Converts the value to a boolean.
 * @param {Boolean} strictTrue only return true if value is strict "true" or "1"
 * @returns {Boolean} The value as boolean
 */
Validation.prototype.toBoolean = function(strictTrue) {
   if (strictTrue) {
      return this.value === "1" || this.value === "true";
   }

   return this.value !== "0" && this.value !== "false" && this.value !== "";
};

// Add functions to the VoidValidation
for (var fn in Validation.prototype) {
   VoidValidation.prototype[fn] = function() { return this; };
}<commit_msg>Drops getTemplateContext()<commit_after>/* The MIT License (MIT)
 *
 * Copyright (c) 2014 Philipp Naderer
 * Copyright (c) 2014 Robert Gaggl
 *
 * 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.
 */

var strings = require("ringo/utils/strings");

/**
 * Creates a new Validator instance
 * @class Instances of this object provide the functionality to
 * validate values and collect messages explaining the validation
 * failure.
 * @returns {Validator} A newly created validator instance
 * @example
 *
 * var validator = new Validator({
 *   "email": "foo.bar@example.com"
 * });
 *
 * // validate a value
 * validator.validate("email").
 *          isDefined("Email address is missing!").
 *          isEmail("Invalid email address provided");
 *
 * // returns true if a validation failed
 * validator.hasErrors();
 *
 * // returns an array with all error messages
 * validator.errorMessages();
 *
 */
var Validator = exports.Validator = function(objectToValidate) {
   /** The object to validate */
   this.obj = objectToValidate;

   /** @ignore */
   this.validations = {};

   /** @ignore */
   this.addValidation = function(key, stopOnFail) {
      return (this.validations[key] = new Validation(key, this.obj[key], stopOnFail));
   };
};

/**
 * Main validation entry point. The result of this method contains
 * various validation methods that can be chained. Note that this
 * validation object stops validating at the first fail.
 * @param {String} name The name of the value
 * @returns {Validation} A Validation instance
 * @see Validator.prototype.validateAll
 */
Validator.prototype.validate = function(key) {
   return this.addValidation(key, true);
};

/**
 * Main validation entry point. The result of this method contains
 * various validation methods that can be chained. Note that this
 * validation object does not stop at the first fail but continues
 * to validate all validation methods.
 * @param {String} name The name of the value
 * @param {Object} value The value to validate
 * @returns {Validation} A Validation instance
 * @see Validator.prototype.validate
 */
Validator.prototype.validateAll = function(key) {
   return this.addValidation(key, false);
};

/**
 * Returns true if any of the validations of this validator failed
 * @param {String} name Optional name of a single value to check
 * @returns {Boolean} True if any (or the specified value) validation failed
 */
Validator.prototype.hasErrors = function(name) {
    if (name != undefined) {
        return !this.validations.hasOwnProperty(name) || !this.validations[name].isValid;
    }
    return true !== Object.keys(this.validations).every(function(key) {
        return this[key].isValid;
    }, this.validations);
};

/**
 * Returns the validation failure messages.
 * @param {String} key Optional key to limit the failure messages to a single validation
 * @returns {Array} An array containing all failure messages
 */
Validator.prototype.errorMessages = function(key) {
   if (key != undefined) {
      return this.hasErrors(key) ? this.validations[key].messages : [];
   }

   var messages = [];
   for each (let validation in this.validations) {
      if (!validation.isValid) {
         messages = messages.concat(validation.messages);
      }
   }
   return messages;
};

var VoidValidation = function(value) {
   this.value = value;
};

/**
 * @class Instances of this class represent a single validation
 * and provide various methods for validating its value.
 * @param {Object} validation An object containing the name of the property,
 *        the value, a validity state and an array of messages.
 * @param {Boolean} stopOnFail If false the all validation
 * method calls in a chain will be executed (optional, defaults to true)
 * @returns {Validation} A newly created validation instance
 * @constructor
 */
var Validation = exports.Validation = function(key, value, stopOnFail) {

   Object.defineProperties(this, {
      "key": { "value": key },
      "value": { "value": value },
      "stopOnFail": { "value": stopOnFail === true },
      "isValid": { "value": true, writable: true },
      "messages": { "value": [], writable: true }
   });

   return this;
};

/** @ignore */
var checkResult = function(validationObj, passes, message) {
   if (!passes) {
      validationObj.isValid = false;
      validationObj.messages.push(message);

      if (validationObj.stopOnFail === true) {
         return new VoidValidation(validationObj.value);
      }
   }

   return validationObj;
};

/**
 * Passes if the value is alphabetic.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isAlpha = function(message) {
   return checkResult(this, strings.isAlpha(this.value), message);
};

/**
 * Passes if the value contains only letters of the alphabet and numbers.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isAlphanumeric = function(message) {
   return checkResult(this, strings.isAlphanumeric(this.value), message);
};

/**
 * Passes if the value is a valid date format pattern.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isDateFormat = function(message) {
   return checkResult(this, strings.isDateFormat(this.value), message);
};

/**
 * Passes if the value is a valid URL.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isUrl = function(message) {
   return checkResult(this, strings.isUrl(this.value), message);
};

/**
 * Passes if the value is a valid email address.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isEmail = function(message) {
   return checkResult(this, strings.isEmail(this.value), message);
};

/**
 * Passes if the value contains no characters
 * that are forbidden in file names.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isFileName = function(message) {
   return checkResult(this, strings.isFileName(this.value), message);
};

/**
 * Passes if the value is a valid color value in hexadecimal format.
 * It may also contain # as first character.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isHexColor = function(message) {
   return checkResult(this, strings.isHexColor(this.value), message);
};

/**
 * Passes if the value contains only the characters [0-9].
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isNumeric = function(message) {
   return checkResult(this, strings.isNumeric(this.value), message);
};

/**
 * Passes if the value is a valid integer literal.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isInt = function(message) {
   return checkResult(this, strings.isInt(this.value), message);
};

/**
 * Passes if the value is a valid floating point literal.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isFloat = function(message) {
   return checkResult(this, strings.isFloat(this.value), message);
};

/**
 * Passes if the value is a valid floating point or integer literal.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isNumber = function(message) {
   return checkResult(this, (strings.isFloat(this.value) || strings.isInt(this.value)), message);
};

/**
 * Passes if the value's length is not shorter than minLength.
 * @param {Number} minLength The minimal length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.minLength = function(minLength, message) {
   return checkResult(this, (this.value.length >= minLength), message);
};

/**
 * Passes if the value's length is not longer than maxLength.
 * @param {Number} maxLength The maximum length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.maxLength = function(maxLength, message) {
   return checkResult(this, (this.value.length <= maxLength), message);
};

/**
 * Passes if the value's length is between the minLength and maxLength.
 * @param {Number} minLength The minimal length of value
 * @param {Number} maxLength The maximum length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.lengthBetween = function(minLength, maxLength, message) {
   return checkResult(this, (this.value.length >= minLength && this.value.length <= maxLength), message);
};

/**
 * Passes if the value has the given length.
 * @param {Number} length The required length of value
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.hasLength = function(length, message) {
   return checkResult(this, (this.value.length === length), message);
};

/**
 * Passes if the value is equal to the given object.
 * @param {Object} obj The object to compare
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.equal = function(obj, message) {
   return checkResult(this, (this.value == obj), message);
};

/**
 * Passes if the value is strict equal to the given object.
 * @param {Object} obj The object to compare
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.strictEqual = function(obj, message) {
   return checkResult(this, (this.value === obj), message);
};

/**
 * Passes if the value is true.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isTrue = function(message) {
   return checkResult(this, this.value, message);
};

/**
 * Passes if the value is false.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isFalse = function(message) {
   return checkResult(this, !this.value, message);
};

/**
 * Passes if the value is not undefined.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.isDefined = function(message) {
   return checkResult(this, this.value !== undefined, message);
};

/**
 * Passes if the value is not null.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.notNull = function(message) {
   return checkResult(this, this.value != null, message);
};

/**
 * Passes if the value is strict not null.
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.strictNotNull = function(message) {
   return checkResult(this, this.value !== null, message);
};

/**
 * Passes if the value matched the given regular expression.
 * @param {RegExp} regex The regular expression to match
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.matches = function(regex, message) {
   return checkResult(this, regex.test(this.value), message);
};

/**
 * Passes if the given function returns true.
 * The function receives the value as first argument.
 * @param {Function} func The validation function
 * @param {String} message The failure message
 * @returns {Validation} This validation instance
 */
Validation.prototype.passes = function(func, message) {
   if (typeof func !== "function") {
      throw "Validator function argument is not a function!";
   }

   return checkResult(this, func(this.value) === true, message);
};

/*** Sanitizers ***/

/**
 * Converts the value to a Date object, if possible.
 * @returns {Date} The value as Date, or null if parsing failed
 */
Validation.prototype.toDate = function() {
   var date = Date.parse(this.value);
   return (!isNaN(date) ? new Date(date) : null);
};

/**
 * Converts the value to a floating point number.
 * @returns {Number} The value as floating point number, or NaN if if parsing failed
 */
Validation.prototype.toFloat = function () {
   if (typeof this.value === "string" && !strings.isFloat(this.value)) {
      return NaN;
   }

   return parseFloat(this.value);
};

/**
 * Converts the value to an integer number.
 * @returns {Number} The value as integer number, or NaN if if parsing failed
 */
Validation.prototype.toInt = function(radix) {
   if (typeof this.value === "string" && !strings.isInt(this.value)) {
      return NaN;
   }

   return parseInt(this.value, radix || 10);
};

/**
 * Converts the value to a boolean.
 * @param {Boolean} strictTrue only return true if value is strict "true" or "1"
 * @returns {Boolean} The value as boolean
 */
Validation.prototype.toBoolean = function(strictTrue) {
   if (strictTrue) {
      return this.value === "1" || this.value === "true";
   }

   return this.value !== "0" && this.value !== "false" && this.value !== "";
};

// Add functions to the VoidValidation
for (var fn in Validation.prototype) {
   VoidValidation.prototype[fn] = function() { return this; };
}<|endoftext|>


<commit_before>var net = require('net');

var Validator = exports.Validator = function() {}

Validator.prototype.check = function(str, fail_msg) {
    this.str = (str == null || (isNaN(str) && str.length == undefined)) ? '' : str;
    // Convert numbers to strings but keep arrays/objects
    if (typeof this.str == 'number') {
      this.str += '';
    }
    this.msg = fail_msg;
    this._errors = [];
    return this;
}

// Helper function to avoid duplication of code
function toDateTime(date) {
    if (date instanceof Date) {
      return date;
    }
    var intDate = Date.parse(date);
    if (isNaN(intDate)) {
        return null;
    }
    return new Date(intDate);
}

// Convert to date without the time component
function toDate(date) {
    if (!(date instanceof Date)) {
      date = toDateTime(date);
    }
    if (!date) {
      return null;
    }
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    return date;
}

//Create some aliases - may help code readability
Validator.prototype.validate = Validator.prototype.check;
Validator.prototype.assert = Validator.prototype.check;

Validator.prototype.error = function(msg) {
    throw new Error(msg);
    return this;
}

Validator.prototype.isEmail = function() {
    if (!this.str.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/)) {
       return this.error(this.msg || 'Invalid email');
    }
    return this;
}

Validator.prototype.isUrl = function() {
    if (!this.str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/)) {
       return this.error(this.msg || 'Invalid URL');
    }
    return this;
}

Validator.prototype.isIP = function() {
    //net.isIP is in node >= 0.3.0
    if (typeof net.isIP === 'function') {
        if (net.isIP(this.str) === 0) {
           return this.error(this.msg || 'Invalid IP');
        }
    } else {
        if (!this.str.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)) {
           return this.error(this.msg || 'Invalid IP');
        }
    }
    return this;
}

Validator.prototype.isAlpha = function() {
    if (!this.str.match(/^[a-zA-Z]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isAlphanumeric = function() {
    if (!this.str.match(/^[a-zA-Z0-9]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNumeric = function() {
    if (!this.str.match(/^-?[0-9]+$/)) {
       return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isLowercase = function() {
    if (!this.str.match(/^[a-z0-9]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isUppercase = function() {
    if (!this.str.match(/^[A-Z0-9]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isInt = function() {
    if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/)) {
       return this.error(this.msg || 'Invalid integer');
    }
    return this;
}

Validator.prototype.isDecimal = function() {
    if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)) {
       return this.error(this.msg || 'Invalid decimal');
    }
    return this;
}

Validator.prototype.isFloat = function() {
    return this.isDecimal();
}

Validator.prototype.notNull = function() {
    if (this.str === '') {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNull = function() {
    if (this.str !== '') {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notEmpty = function() {
    if (this.str.match(/^[\s\t\r\n]*$/)) {
       return this.error(this.msg || 'String is empty');
    }
    return this;
}

Validator.prototype.equals = function(equals) {
    if (this.str != equals) {
       return this.error(this.msg || 'Not equal');
    }
    return this;
}

Validator.prototype.contains = function(str) {
    if (this.str.indexOf(str) === -1) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notContains = function(str) {
    if (this.str.indexOf(str) >= 0) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (! this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.len = function(min, max) {
    if (this.str.length < min) {
       return this.error(this.msg || 'String is too small');
    }
    if (typeof max !== undefined && this.str.length > max) {
       return this.error(this.msg || 'String is too large');
    }
    return this;
}

//Thanks to github.com/sreuter for the idea.
Validator.prototype.isUUID = function(version) {
    if (version == 3 || version == 'v3') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    } else if (version == 4 || version == 'v4') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
    } else {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    }
    if (!this.str.match(pattern)) {
       return this.error(this.msg || 'Not a UUID');
    }
    return this;
}

Validator.prototype.isDate = function() {
    var intDate = Date.parse(this.str);
    if (isNaN(intDate)) {
        return this.error(this.msg || 'Not a date');
    }
    return this;
}

Validator.prototype.in = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (!~options.indexOf(this.str)) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid in() argument');
    }
}

Validator.prototype.notIn = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (options.indexOf(this.str) !== -1) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid notIn() argument');
    }
}

Validator.prototype.min = function(val) {
    var number = parseFloat(this.str);
    if (!isNaN(number) && number < val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.max = function(val) {
    var number = parseFloat(this.str);
    if (!isNaN(number) && number > val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isArray = function() {
    if (!Array.isArray(this.str)) {
        return this.error(this.msg || 'Not an array');
    }
    return this;
}
<commit_msg>Implemented isAfter validator that passes the test.<commit_after>var net = require('net');

var Validator = exports.Validator = function() {}

Validator.prototype.check = function(str, fail_msg) {
    this.str = (str == null || (isNaN(str) && str.length == undefined)) ? '' : str;
    // Convert numbers to strings but keep arrays/objects
    if (typeof this.str == 'number') {
      this.str += '';
    }
    this.msg = fail_msg;
    this._errors = [];
    return this;
}

// Helper function to avoid duplication of code
function toDateTime(date) {
    if (date instanceof Date) {
      return date;
    }
    var intDate = Date.parse(date);
    if (isNaN(intDate)) {
        return null;
    }
    return new Date(intDate);
}

// Convert to date without the time component
function toDate(date) {
    if (!(date instanceof Date)) {
      date = toDateTime(date);
    }
    if (!date) {
      return null;
    }
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    return date;
}

//Create some aliases - may help code readability
Validator.prototype.validate = Validator.prototype.check;
Validator.prototype.assert = Validator.prototype.check;

Validator.prototype.error = function(msg) {
    throw new Error(msg);
    return this;
}

Validator.prototype.isEmail = function() {
    if (!this.str.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/)) {
       return this.error(this.msg || 'Invalid email');
    }
    return this;
}

Validator.prototype.isUrl = function() {
    if (!this.str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/)) {
       return this.error(this.msg || 'Invalid URL');
    }
    return this;
}

Validator.prototype.isIP = function() {
    //net.isIP is in node >= 0.3.0
    if (typeof net.isIP === 'function') {
        if (net.isIP(this.str) === 0) {
           return this.error(this.msg || 'Invalid IP');
        }
    } else {
        if (!this.str.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)) {
           return this.error(this.msg || 'Invalid IP');
        }
    }
    return this;
}

Validator.prototype.isAlpha = function() {
    if (!this.str.match(/^[a-zA-Z]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isAlphanumeric = function() {
    if (!this.str.match(/^[a-zA-Z0-9]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNumeric = function() {
    if (!this.str.match(/^-?[0-9]+$/)) {
       return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isLowercase = function() {
    if (!this.str.match(/^[a-z0-9]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isUppercase = function() {
    if (!this.str.match(/^[A-Z0-9]+$/)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isInt = function() {
    if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/)) {
       return this.error(this.msg || 'Invalid integer');
    }
    return this;
}

Validator.prototype.isDecimal = function() {
    if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)) {
       return this.error(this.msg || 'Invalid decimal');
    }
    return this;
}

Validator.prototype.isFloat = function() {
    return this.isDecimal();
}

Validator.prototype.notNull = function() {
    if (this.str === '') {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNull = function() {
    if (this.str !== '') {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notEmpty = function() {
    if (this.str.match(/^[\s\t\r\n]*$/)) {
       return this.error(this.msg || 'String is empty');
    }
    return this;
}

Validator.prototype.equals = function(equals) {
    if (this.str != equals) {
       return this.error(this.msg || 'Not equal');
    }
    return this;
}

Validator.prototype.contains = function(str) {
    if (this.str.indexOf(str) === -1) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notContains = function(str) {
    if (this.str.indexOf(str) >= 0) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (! this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.len = function(min, max) {
    if (this.str.length < min) {
       return this.error(this.msg || 'String is too small');
    }
    if (typeof max !== undefined && this.str.length > max) {
       return this.error(this.msg || 'String is too large');
    }
    return this;
}

//Thanks to github.com/sreuter for the idea.
Validator.prototype.isUUID = function(version) {
    if (version == 3 || version == 'v3') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    } else if (version == 4 || version == 'v4') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
    } else {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    }
    if (!this.str.match(pattern)) {
       return this.error(this.msg || 'Not a UUID');
    }
    return this;
}

Validator.prototype.isDate = function() {
    var intDate = Date.parse(this.str);
    if (isNaN(intDate)) {
        return this.error(this.msg || 'Not a date');
    }
    return this;
}

Validator.prototype.isAfter = function(date) {
    date = date || new Date();
    var origDate = toDate(this.str);
    var compDate = toDate(date);

    if (origDate && compDate && origDate < compDate) {
        return this.error(this.msg || 'Invalid date');
    }

    return this;
}

Validator.prototype.in = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (!~options.indexOf(this.str)) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid in() argument');
    }
}

Validator.prototype.notIn = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (options.indexOf(this.str) !== -1) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid notIn() argument');
    }
}

Validator.prototype.min = function(val) {
    var number = parseFloat(this.str);
    if (!isNaN(number) && number < val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.max = function(val) {
    var number = parseFloat(this.str);
    if (!isNaN(number) && number > val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isArray = function() {
    if (!Array.isArray(this.str)) {
        return this.error(this.msg || 'Not an array');
    }
    return this;
}

<|endoftext|>


<commit_before>$(document).ready(function() {
    'use strict';

    /* Graphs Initialization */
    var margin = {top: 20, right: 50, bottom: 50, left: 50};
    margin.vertical = margin.top + margin.bottom;
    margin.horizontal = margin.left + margin.right;
    var width = 960 - margin.horizontal,
    height = 350 - margin.vertical;

    function weekify(week) {
        var offset = 12 - week;
        var d = new Date();
        d.setDate(d.getDate() - 7*offset);
        var y = d.getFullYear();
        var onejan = new Date(y, 0, 1);
        var w = Math.ceil((((d - onejan) / 86400000) + onejan.getDay() + 1) / 7);
        return y + 'w' + w;
    }

    function people_graph(key, value) {
        var x = d3.scale.linear()
                  .range([0, width]);

        var y = d3.scale.linear()
                  .range([height, 0]);

        var xAxis = d3.svg.axis()
                      .scale(x)
                      .tickFormat(weekify)
                      .orient('bottom');

        var yAxis = d3.svg.axis()
                      .scale(y)
                      .tickFormat(d3.format('d'))
                      .orient('left');

        var area_inactive = d3.svg.area()
                              .x(function(d) { return x(d.week); })
                              .y0(height)
                              .y1(function(d) { return y(d.inactive); });

        var area_active = d3.svg.area()
                                .x(function(d) { return x(d.week); })
                                .y0(height)
                                .y1(function(d) { return y(d.active_stack); });

        var area_casual = d3.svg.area()
                                .x(function(d) { return x(d.week); })
                                .y0(height)
                                .y1(function(d) { return y(d.casual_stack); });

        var area_core = d3.svg.area()
                          .x(function(d) { return x(d.week); })
                          .y0(height)
                          .y1(function(d) { return y(d.core_stack); });

        $('#graph-people').text('');
        var svg = d3.select('#graph-people').append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var tip = d3.tip()
                    .attr('class', 'd3-tip')
                    .offset([35, 0])
                    .html(function(d) {
                        return 'Inactive: '
                               + d.inactive + '
' +
                               'Active: '
                               + d.active + '
' +
                               'Casual: '
                               + d.casual + '
' +
                               'Core: '
                               + d.core + '
' +
                               'Total: '
                               + d.active_total + '';
                    });

        svg.call(tip);

        var apiurl = '/api/kpi/people/?format=json&'
        if ((typeof(key)!='undefined') && (typeof(value)!='undefined')) {
            apiurl += key + '=' + value;
        }
        d3.json(apiurl, function(error, data) {
            $('#people-total').text(data.total);
            $('#people-total_quarter').text(data.quarter_total);
            $('#people-total_week').text(data.week_total);
            $('#people-percentage_week').text(data.week_growth_percentage);
            $('#people-percentage_quarter').text(data.quarter_growth_percentage);

            var weeks = data.total_per_week;

            weeks.forEach(function(d) {
                d.week = +d.week;
                d.core = +d.core;
                d.active = +d.active;
                d.casual = +d.casual;
                d.inactive = +d.inactive;
                d.active_stack = d.active + d.inactive;
                d.casual_stack = d.casual + d.active_stack;
                d.core_stack = d.core + d.casual_stack;
                d.active_total = d.core + d.active + d.casual + d.inactive;
            });

            var formatPercent = d3.format('%U');

            x.domain([1, d3.max(weeks, function(d) { return d.week; })]);
            y.domain([0, d3.max(weeks, function(d) { return d.active_total; })]);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-core')
               .attr('d', area_core);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-casual')
               .attr('d', area_casual);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-active')
               .attr('d', area_active);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-inactive')
               .attr('d', area_inactive);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            svg.append('g')
               .attr('class', 'y axis')
               .call(yAxis)
               .append('text');

            svg.append('text')
               .attr('transform', 'rotate(-90)')
               .attr('y', -50)
               .attr('x', -40 - (height / 2))
               .attr('dy','1em')
               .attr('class', 'axis-label')
               .text('Number of Reps');

            svg.append('text')
               .attr('x', (width / 2) - 40)
               .attr('y', height + 40)
               .attr('text-anchor', 'middle')
               .attr('class', 'axis-label')
               .text('Weeks');

            var width_block = width / (weeks.length - 1);

            svg.selectAll('.area-people')
               .data(weeks)
               .enter()
               .append('rect')
               .attr('class', 'info-area')
               .attr('x', function (d) { return ((d.week - 1) * width_block - 20) })
               .attr('y', function (d) { return 0; })
               .attr('height', function (d) { return height; })
               .attr('width', '40px')
               .on('mouseover', function (d, i) {
                   tip.show(d);
                   d3.select(this).style('opacity', 0.2);
               })
               .on('mouseout', function (d, i) {
                   tip.hide(d);
                   d3.select(this).style('opacity', 0);
               });
        });
    }

    function events_graph(key, value) {
        var x = d3.scale.linear()
                  .range([0, width]);

        var y = d3.scale.linear()
                  .range([height, 0]);

        var xAxis = d3.svg.axis()
                      .scale(x)
                      .tickFormat(weekify)
                      .orient('bottom');

        var yAxis = d3.svg.axis()
                      .scale(y)
                      .tickFormat(d3.format('d'))
                      .orient('left');

        var area = d3.svg.area()
                     .x(function(d) { return x(d.week); })
                     .y0(height)
                     .y1(function(d) { return y(d.events); });

        $('#graph-events').text('');
        var svg = d3.select('#graph-events').append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var tip = d3.tip()
                    .attr('class', 'd3-tip')
                    .offset([0, 0])
                    .html(function(d) {
                        return 'Events: ' + d.events + '';
                    });

        svg.call(tip);

        var apiurl = '/api/kpi/events/?format=json&'
        if ((typeof(key)!='undefined') && (typeof(value)!='undefined')) {
            apiurl += key + '=' + value;
        }
        d3.json(apiurl, function(error, data) {
            $('#events-total').text(data.total);
            $('#events-total_quarter').text(data.quarter_total);
            $('#events-total_week').text(data.week_total);
            $('#events-percentage_week').text(data.week_growth_percentage);
            $('#events-percentage_quarter').text(data.quarter_growth_percentage);

            var weeks = data.total_per_week;

            weeks.forEach(function(d) {
                d.week = +d.week;
                d.events = +d.events;
            });

            var formatPercent = d3.format('%U');

            x.domain([1, d3.max(weeks, function(d) { return d.week; })]);
            y.domain([0, d3.max(weeks, function(d) { return d.events; })]);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-events')
               .attr('d', area);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            svg.append('g')
               .attr('class', 'y axis')
               .call(yAxis)
               .append('text');

            svg.append('text')
               .attr('transform', 'rotate(-90)')
               .attr('y', -50)
               .attr('x', -40 - (height / 2))
               .attr('dy','1em')
               .attr('class', 'axis-label')
               .text('Number of Events');

            svg.append('text')
               .attr('x', (width / 2) - 40)
               .attr('y', height + 40)
               .attr('text-anchor', 'middle')
               .attr('class', 'axis-label')
               .text('Weeks');

            var width_block = width / (weeks.length - 1);

            svg.selectAll('.line-events')
               .data(weeks)
               .enter()
               .append('rect')
               .attr('class', 'info-area')
               .attr('id', function (d) { return 'w' + d.week; })
               .attr('x', function (d) { return ((d.week - 1) * width_block - 20) })
               .attr('y', function (d) { return 0; })
               .attr('height', function (d) { return height; })
               .attr('width', '40px')
               .on('mouseover', function (d, i) {
                   tip.show(d);
                   d3.select(this).style('opacity', 0.2);
               })
               .on('mouseout', function (d, i) {
                   tip.hide(d);
                   d3.select(this).style('opacity', 0);
               });
        });
    }

    function activities_graph(key, value) {
        var x = d3.scale.linear()
                  .range([0, width]);

        var y = d3.scale.linear()
                  .range([height, 0]);

        var xAxis = d3.svg.axis()
                      .scale(x)
                      .tickFormat(weekify)
                      .orient('bottom');

        var yAxis = d3.svg.axis()
                      .scale(y)
                      .tickFormat(d3.format('d'))
                      .orient('left');

        var area = d3.svg.area()
                     .x(function(d) { return x(d.week); })
                     .y0(height)
                     .y1(function(d) { return y(d.activities); });

        $('#graph-activities').text('');
        var svg = d3.select('#graph-activities').append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var tip = d3.tip()
                    .attr('class', 'd3-tip')
                    .offset([35, 0])
                    .html(function(d) {
                        return 'Activities: ' + d.activities + '';
                    });

        svg.call(tip);

        var apiurl = '/api/kpi/activities/?format=json&'
        if ((typeof(key)!='undefined') && (typeof(value)!='undefined')) {
            apiurl += key + '=' + value;
        }
        d3.json(apiurl, function(error, data) {
            $('#activities-total').text(data.total);
            $('#activities-total_quarter').text(data.quarter_total);
            $('#activities-total_week').text(data.week_total);
            $('#activities-percentage_week').text(data.week_growth_percentage);
            $('#activities-percentage_quarter').text(data.quarter_growth_percentage);

            var weeks = data.total_per_week;

            weeks.forEach(function(d) {
                d.week = +d.week;
                d.activities = +d.activities;
            });

            var formatPercent = d3.format('%U');

            x.domain([1, d3.max(weeks, function(d) { return d.week; })]);
            y.domain([0, d3.max(weeks, function(d) { return d.activities; })]);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-activities')
               .attr('d', area);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            svg.append('g')
               .attr('class', 'y axis')
               .call(yAxis);

            svg.append('text')
               .attr('transform', 'rotate(-90)')
               .attr('y', -50)
               .attr('x', -40 - (height / 2))
               .attr('dy','1em')
               .attr('class', 'axis-label')
               .text('Number of Activities');

            svg.append('text')
               .attr('x', (width / 2) - 40)
               .attr('y', height + 40)
               .attr('text-anchor', 'middle')
               .attr('class', 'axis-label')
               .text('Weeks');

            var width_block = width / (weeks.length - 1);

            svg.selectAll('.line-activities')
               .data(weeks)
               .enter()
               .append('rect')
               .attr('class', 'info-area')
               .attr('x', function (d) { return ((d.week - 1) * width_block - 20) })
               .attr('y', function (d) { return 0; })
               .attr('height', function (d) { return height; })
               .attr('width', '40px')
               .on('mouseover', function (d, i) {
                   tip.show(d);
                   d3.select(this).style('opacity', 0.2);
               })
               .on('mouseout', function (d, i) {
                   tip.hide(d);
                   d3.select(this).style('opacity', 0);
               });
        });
    }

    /* Draw all graphs */
    people_graph();
    events_graph();
    activities_graph();

    /* Filtering */
    $('select').change(function() {
        $('.d3-graph').html('
loading...
');
        var currentId = $(this).attr('id');
        var value = $('#' + currentId + ' option:selected').val();
        var key = $(this).data('query');
        people_graph(key, value);
        events_graph(key, value);
        activities_graph(key, value);
    });
});
<commit_msg>Enable filtering when multiple filters are selected.<commit_after>$(document).ready(function() {
    'use strict';

    /* Graphs Initialization */
    var margin = {top: 20, right: 50, bottom: 50, left: 50};
    margin.vertical = margin.top + margin.bottom;
    margin.horizontal = margin.left + margin.right;
    var width = 960 - margin.horizontal,
    height = 350 - margin.vertical;

    function weekify(week) {
        var offset = 12 - week;
        var d = new Date();
        d.setDate(d.getDate() - 7*offset);
        var y = d.getFullYear();
        var onejan = new Date(y, 0, 1);
        var w = Math.ceil((((d - onejan) / 86400000) + onejan.getDay() + 1) / 7);
        return y + 'w' + w;
    }

    function urlparams(url, params) {
        $.each(params, function(index, elem) {
            if (elem.value) {
                url += elem.key + '=' + elem.value + '&';
            }
        });

        return url;
    }

    function people_graph(filters) {
        var x = d3.scale.linear()
                  .range([0, width]);

        var y = d3.scale.linear()
                  .range([height, 0]);

        var xAxis = d3.svg.axis()
                      .scale(x)
                      .tickFormat(weekify)
                      .orient('bottom');

        var yAxis = d3.svg.axis()
                      .scale(y)
                      .tickFormat(d3.format('d'))
                      .orient('left');

        var area_inactive = d3.svg.area()
                              .x(function(d) { return x(d.week); })
                              .y0(height)
                              .y1(function(d) { return y(d.inactive); });

        var area_active = d3.svg.area()
                                .x(function(d) { return x(d.week); })
                                .y0(height)
                                .y1(function(d) { return y(d.active_stack); });

        var area_casual = d3.svg.area()
                                .x(function(d) { return x(d.week); })
                                .y0(height)
                                .y1(function(d) { return y(d.casual_stack); });

        var area_core = d3.svg.area()
                          .x(function(d) { return x(d.week); })
                          .y0(height)
                          .y1(function(d) { return y(d.core_stack); });

        $('#graph-people').text('');
        var svg = d3.select('#graph-people').append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var tip = d3.tip()
                    .attr('class', 'd3-tip')
                    .offset([35, 0])
                    .html(function(d) {
                        return 'Inactive: '
                               + d.inactive + '
' +
                               'Active: '
                               + d.active + '
' +
                               'Casual: '
                               + d.casual + '
' +
                               'Core: '
                               + d.core + '
' +
                               'Total: '
                               + d.active_total + '';
                    });

        svg.call(tip);

        var apiurl = urlparams('/api/kpi/people/?format=json&', filters);
        d3.json(apiurl, function(error, data) {
            $('#people-total').text(data.total);
            $('#people-total_quarter').text(data.quarter_total);
            $('#people-total_week').text(data.week_total);
            $('#people-percentage_week').text(data.week_growth_percentage);
            $('#people-percentage_quarter').text(data.quarter_growth_percentage);

            var weeks = data.total_per_week;

            weeks.forEach(function(d) {
                d.week = +d.week;
                d.core = +d.core;
                d.active = +d.active;
                d.casual = +d.casual;
                d.inactive = +d.inactive;
                d.active_stack = d.active + d.inactive;
                d.casual_stack = d.casual + d.active_stack;
                d.core_stack = d.core + d.casual_stack;
                d.active_total = d.core + d.active + d.casual + d.inactive;
            });

            var formatPercent = d3.format('%U');

            x.domain([1, d3.max(weeks, function(d) { return d.week; })]);
            y.domain([0, d3.max(weeks, function(d) { return d.active_total; })]);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-core')
               .attr('d', area_core);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-casual')
               .attr('d', area_casual);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-active')
               .attr('d', area_active);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-people-inactive')
               .attr('d', area_inactive);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            svg.append('g')
               .attr('class', 'y axis')
               .call(yAxis)
               .append('text');

            svg.append('text')
               .attr('transform', 'rotate(-90)')
               .attr('y', -50)
               .attr('x', -40 - (height / 2))
               .attr('dy','1em')
               .attr('class', 'axis-label')
               .text('Number of Reps');

            svg.append('text')
               .attr('x', (width / 2) - 40)
               .attr('y', height + 40)
               .attr('text-anchor', 'middle')
               .attr('class', 'axis-label')
               .text('Weeks');

            var width_block = width / (weeks.length - 1);

            svg.selectAll('.area-people')
               .data(weeks)
               .enter()
               .append('rect')
               .attr('class', 'info-area')
               .attr('x', function (d) { return ((d.week - 1) * width_block - 20) })
               .attr('y', function (d) { return 0; })
               .attr('height', function (d) { return height; })
               .attr('width', '40px')
               .on('mouseover', function (d, i) {
                   tip.show(d);
                   d3.select(this).style('opacity', 0.2);
               })
               .on('mouseout', function (d, i) {
                   tip.hide(d);
                   d3.select(this).style('opacity', 0);
               });
        });
    }

    function events_graph(filters) {
        var x = d3.scale.linear()
                  .range([0, width]);

        var y = d3.scale.linear()
                  .range([height, 0]);

        var xAxis = d3.svg.axis()
                      .scale(x)
                      .tickFormat(weekify)
                      .orient('bottom');

        var yAxis = d3.svg.axis()
                      .scale(y)
                      .tickFormat(d3.format('d'))
                      .orient('left');

        var area = d3.svg.area()
                     .x(function(d) { return x(d.week); })
                     .y0(height)
                     .y1(function(d) { return y(d.events); });

        $('#graph-events').text('');
        var svg = d3.select('#graph-events').append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var tip = d3.tip()
                    .attr('class', 'd3-tip')
                    .offset([0, 0])
                    .html(function(d) {
                        return 'Events: ' + d.events + '';
                    });

        svg.call(tip);

        var apiurl = urlparams('/api/kpi/events/?format=json&', filters);
        d3.json(apiurl, function(error, data) {
            $('#events-total').text(data.total);
            $('#events-total_quarter').text(data.quarter_total);
            $('#events-total_week').text(data.week_total);
            $('#events-percentage_week').text(data.week_growth_percentage);
            $('#events-percentage_quarter').text(data.quarter_growth_percentage);

            var weeks = data.total_per_week;

            weeks.forEach(function(d) {
                d.week = +d.week;
                d.events = +d.events;
            });

            var formatPercent = d3.format('%U');

            x.domain([1, d3.max(weeks, function(d) { return d.week; })]);
            y.domain([0, d3.max(weeks, function(d) { return d.events; })]);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-events')
               .attr('d', area);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            svg.append('g')
               .attr('class', 'y axis')
               .call(yAxis)
               .append('text');

            svg.append('text')
               .attr('transform', 'rotate(-90)')
               .attr('y', -50)
               .attr('x', -40 - (height / 2))
               .attr('dy','1em')
               .attr('class', 'axis-label')
               .text('Number of Events');

            svg.append('text')
               .attr('x', (width / 2) - 40)
               .attr('y', height + 40)
               .attr('text-anchor', 'middle')
               .attr('class', 'axis-label')
               .text('Weeks');

            var width_block = width / (weeks.length - 1);

            svg.selectAll('.line-events')
               .data(weeks)
               .enter()
               .append('rect')
               .attr('class', 'info-area')
               .attr('id', function (d) { return 'w' + d.week; })
               .attr('x', function (d) { return ((d.week - 1) * width_block - 20) })
               .attr('y', function (d) { return 0; })
               .attr('height', function (d) { return height; })
               .attr('width', '40px')
               .on('mouseover', function (d, i) {
                   tip.show(d);
                   d3.select(this).style('opacity', 0.2);
               })
               .on('mouseout', function (d, i) {
                   tip.hide(d);
                   d3.select(this).style('opacity', 0);
               });
        });
    }

    function activities_graph(filters) {
        var x = d3.scale.linear()
                  .range([0, width]);

        var y = d3.scale.linear()
                  .range([height, 0]);

        var xAxis = d3.svg.axis()
                      .scale(x)
                      .tickFormat(weekify)
                      .orient('bottom');

        var yAxis = d3.svg.axis()
                      .scale(y)
                      .tickFormat(d3.format('d'))
                      .orient('left');

        var area = d3.svg.area()
                     .x(function(d) { return x(d.week); })
                     .y0(height)
                     .y1(function(d) { return y(d.activities); });

        $('#graph-activities').text('');
        var svg = d3.select('#graph-activities').append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var tip = d3.tip()
                    .attr('class', 'd3-tip')
                    .offset([35, 0])
                    .html(function(d) {
                        return 'Activities: ' + d.activities + '';
                    });

        svg.call(tip);

        var apiurl = urlparams('/api/kpi/activities/?format=json&', filters);
        d3.json(apiurl, function(error, data) {
            $('#activities-total').text(data.total);
            $('#activities-total_quarter').text(data.quarter_total);
            $('#activities-total_week').text(data.week_total);
            $('#activities-percentage_week').text(data.week_growth_percentage);
            $('#activities-percentage_quarter').text(data.quarter_growth_percentage);

            var weeks = data.total_per_week;

            weeks.forEach(function(d) {
                d.week = +d.week;
                d.activities = +d.activities;
            });

            var formatPercent = d3.format('%U');

            x.domain([1, d3.max(weeks, function(d) { return d.week; })]);
            y.domain([0, d3.max(weeks, function(d) { return d.activities; })]);

            svg.append('path')
               .datum(weeks)
               .attr('class', 'area-activities')
               .attr('d', area);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            svg.append('g')
               .attr('class', 'y axis')
               .call(yAxis);

            svg.append('text')
               .attr('transform', 'rotate(-90)')
               .attr('y', -50)
               .attr('x', -40 - (height / 2))
               .attr('dy','1em')
               .attr('class', 'axis-label')
               .text('Number of Activities');

            svg.append('text')
               .attr('x', (width / 2) - 40)
               .attr('y', height + 40)
               .attr('text-anchor', 'middle')
               .attr('class', 'axis-label')
               .text('Weeks');

            var width_block = width / (weeks.length - 1);

            svg.selectAll('.line-activities')
               .data(weeks)
               .enter()
               .append('rect')
               .attr('class', 'info-area')
               .attr('x', function (d) { return ((d.week - 1) * width_block - 20) })
               .attr('y', function (d) { return 0; })
               .attr('height', function (d) { return height; })
               .attr('width', '40px')
               .on('mouseover', function (d, i) {
                   tip.show(d);
                   d3.select(this).style('opacity', 0.2);
               })
               .on('mouseout', function (d, i) {
                   tip.hide(d);
                   d3.select(this).style('opacity', 0);
               });
        });
    }

    /* Draw all graphs */
    var filters = [];
    people_graph(filters);
    events_graph(filters);
    activities_graph(filters);

    /* Filtering */
    $('select').change(function() {
        $('.d3-graph').html('
loading...
');

        var filters = [];
        $('select').each(function() {
            var currentId = $(this).attr('id');
            var key = $(this).data('query');
            var value = $('#' + currentId + ' option:selected').val();
            filters.push({'key': key, 'value': value});
        });

        people_graph(filters);
        events_graph(filters);
        activities_graph(filters);
    });
});
<|endoftext|>