From ccd94b3ef95e7d4e79b6cb0b4b2ef82681ecdef4 Mon Sep 17 00:00:00 2001 From: Howard Richards Date: Thu, 21 Nov 2013 22:25:38 +0000 Subject: [PATCH] Added Valerie initial version Redone as I messed up my local repository --- README.md | 1 + valerie/valerie-tests.ts | 286 +++++++++++++++++++++++ valerie/valerie.d.ts | 482 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 769 insertions(+) create mode 100644 valerie/valerie-tests.ts create mode 100644 valerie/valerie.d.ts diff --git a/README.md b/README.md index 2512c1f17e..92996a9ca8 100755 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ List of Definitions * [Underscore.js (Typed)](http://underscorejs.org/) (by [Josh Baldwin](https://github.com/jbaldwin/)) * [Underscore-ko.js](https://github.com/kamranayub/UnderscoreKO) (by [Maurits Elbers](https://github.com/MagicMau)) * [UUID.js](https://github.com/LiosK/UUID.js) (by [Jason Jarrett](https://github.com/staxmanade)) +* [Valerie](https://github.com/davewatts/valerie) (by [Howard Richards](https://github.com/conficient)) * [Viewporter](https://github.com/zynga/viewporter) (by [Boris Yankov](https://github.com/borisyankov)) * [Vimeo](http://developer.vimeo.com/player/js-api) (by [Daz Wilkin](https://github.com/DazWilkin/)) * [WebRTC](http://dev.w3.org/2011/webrtc/editor/webrtc.html) (by [Ken Smith](https://github.com/smithkl42)) diff --git a/valerie/valerie-tests.ts b/valerie/valerie-tests.ts new file mode 100644 index 0000000000..e826b24c93 --- /dev/null +++ b/valerie/valerie-tests.ts @@ -0,0 +1,286 @@ +/// +/// + +// Tests for valerie.d.ts +// Project: https://github.com/davewatts/valerie +// Definitions by: Howard Richards +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/* + Checks the .d.ts definition work. Not a fully comprehensive set of tests yet. +*/ + +/** + * Simple enum for enum test + */ +enum enumTest { male, female } + +/** + * ensure that observable values can validate + * + */ +function ObservableValidationTypes() { + // any + var t0 = ko.observable() + .validate() + .end(); + + // string + var t1 = ko.observable("") + .validate() + .end(); + + // number + var t2 = ko.observable(0) + .validate() + .end(); + + // bool + var t3 = ko.observable(true) + .validate() + .end(); + + // date + var t4 = ko.observable(new Date()) + .validate() + .end(); + + // enum + var t5 = ko.observable(enumTest.male) + .validate() + .end(); + + //array + var t6 = ko.observableArray([]) + .validate() + .end(); +} + +function ComputedValidationTests() { + var t1 = ko.computed(function () { return "hello world"; }) + .validate() + .end(); +} + +function RuleTests() { + + // various values used in rule tests + var dummyRule: Valerie.IRule = { test: null, defaultOptions: null }; + + var stringValue = ""; + var stringFN = function () { return ""; } + + var numberValue = 1; + var numberFN = function () { return 2 } + + var booleanValue = false; + var booleanFN = ko.observable(true); + + var dateValue = new Date(); + var dateFN = function () { return new Date(); } + + var anyValue = {}; + var anyFN = function () { return {}; } + + var arrayValue = []; + var arrayFN = function () { return []; } + + var regexpValue = /\d+/; + + // rule tests + + var test_addRule = ko.observable(stringValue) + .validate() + .addRule(dummyRule) + .end(); + + var test_applicable = ko.observable(stringValue) + .validate() + .applicable(true) + .applicable(function () { return false; }) + .end(); + + var test_currencyMajor = ko.observable(numberValue) + .validate() + .currencyMajor() + .end(); + + var test_currencyMinor = ko.observable(numberValue) + .validate() + .currencyMajorMinor() + .end(); + + var test_date = ko.observable(dateValue) + .validate() + .date() + .end(); + + var test_during = ko.observable(dateValue) + .validate() + .during(dateValue, dateValue) + .during(dateFN, dateFN) + .during(dateValue, dateFN) + .during(dateFN, dateValue) + .end(); + + var test_earliest = ko.observable(dateValue) + .validate() + .earliest(dateValue) + .end(); + + var test_email = ko.observable(stringValue) + .validate() + .email() + .end(); + + var test_entryformat = ko.observable(stringValue) + .validate() + .entryFormat(stringValue) + .end(); + + var test_expression = ko.observable(stringValue) + .validate() + .expression(regexpValue) + .expression(stringValue) + .end(); + + var test_float = ko.observable(numberValue) + .validate() + .float() + .end(); + + var test_integer = ko.observable(numberValue) + .validate() + .integer() + .end(); + + var test_latest = ko.observable(dateValue) + .validate() + .latest(dateValue) + .latest(dateFN) + .end(); + + var test_lengthBetween = ko.observable(stringValue) + .validate() + .lengthBetween(numberValue, numberValue) + .lengthBetween(numberFN, numberFN) + .lengthBetween(numberFN, numberValue) + .lengthBetween(numberValue, numberFN) + .end(); + + var test_matches = ko.observable(stringValue) + .validate() + .matches(numberValue) + .matches(numberFN) + .end(); + + var test_maximum = ko.observable(0) + .validate() + .maximum(numberValue) + .maximum(numberFN) + .end(); + + var test_maximumNumberOfItems = ko.observableArray([]) + .validate() + .maximumNumberOfItems(numberValue) + .maximumNumberOfItems(numberFN) + .end(); + + var test_minimum = ko.observable(numberValue) + .validate() + .minimum(numberValue) + .minimum(numberFN) + .end(); + + var test_minimumLength = ko.observable("") + .validate() + .minimumLength(numberValue) + .minimumLength(numberFN) + .end(); + + var test_minimumNumberOfItems = ko.observableArray([]) + .validate() + .minimumNumberOfItems(numberValue) + .minimumNumberOfItems(numberFN) + .end(); + + var test_name = ko.observable(stringValue) + .validate() + .name(stringValue) + .end(); + + var test_noneOf = ko.observable(numberValue) + .validate() + .noneOf(arrayValue) + .noneOf(arrayFN) + .end(); + + var test_not = ko.observable(numberValue) + .validate() + .not(anyValue) + .not(anyFN) + .end(); + + var test_number = ko.observable(numberValue) + .validate() + .number() + .end(); + + var test_numberOfItems = ko.observableArray(arrayValue) + .validate() + .numberOfItems(numberValue, numberValue) + .numberOfItems(numberFN, numberFN) + .numberOfItems(numberFN, numberValue) + .numberOfItems(numberValue, numberFN) + .end(); + + var test_oneOf = ko.observable(numberValue) + .validate() + .oneOf(arrayValue) + .oneOf(arrayFN) + .end(); + + var test_postcode = ko.observable(stringValue) + .validate() + .postcode() + .end(); + + var test_range = ko.observable(numberValue) + .validate() + .range(numberValue, numberValue) + .range(numberValue, numberFN) + .range(numberFN, numberValue) + .range(numberFN, numberFN) + .end(); + + var test_required = ko.observable(anyValue) + .validate() + .required() + .end(); + + var test_ruleMessage = ko.observable(anyValue) + .validate() + .ruleMessage(stringValue) + .end(); + + var test_string = ko.observable(stringValue) + .validate() + .string() + .end(); + + var test_validateChildProperties = ko.observable(anyValue) + .validate() + .validateChildProperties() + .end(); + + var test_valueFormat = ko.observable(anyValue) + .validate() + .valueFormat(stringValue) + .end(); + + var test_rule = ko.observable(anyValue) + .validate() + .rule(() => { return anyValue; }) + .end(); + +} \ No newline at end of file diff --git a/valerie/valerie.d.ts b/valerie/valerie.d.ts new file mode 100644 index 0000000000..75afeaa115 --- /dev/null +++ b/valerie/valerie.d.ts @@ -0,0 +1,482 @@ +// Type definitions for valerie +// Project: https://github.com/davewatts/valerie +// Definitions by: Howard Richards +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/** + * + * Extensions to KO functions to provide validation + */ +interface KnockoutObservable { + // starts validation for observable + validate(validationOptions?: Valerie.ValidationOptions): Valerie.PropertyValidationState>; +} + +interface KnockoutComputed { + // starts validation for observable + validate(validationOptions?: Valerie.ValidationOptions): Valerie.PropertyValidationState>; +} + +interface KnockoutObservableArray { + validate(validationOptions?: Valerie.ValidationOptions): Valerie.PropertyValidationState>; + // starts model validation for array + validateAsModel(): Valerie.ValidatableModel>; +} + +/** +* Valerie BindingHandlers +*/ +interface KnockoutBindingHandlers { + /** + * Validates entries that can be checked, i.e. check boxes and radio buttons. + * Functions in the same way as the ko.bindingHandlers.checked binding handler, with the following + * alterations: + *
    + *
  • registers a blur event handler so validation messages for selections can be displayed
  • + *
  • registers a click event handler so validation state can be marked as touched + *
+ * @name ko.bindingHandlers.validatedChecked + */ + validatedChecked: KnockoutBindingHandler; + + /** + * Validates options selected in a select list. + * Functions in the same way as the ko.bindingHandlers.selectedOptions binding handler, with the + * following alterations: + *
    + *
  • registers a blur event handler so validation messages for selections can be displayed
  • + *
  • registers a click event handler so validation state can be marked as touched + *
+ * @name ko.bindingHandlers.validatedSelectedOptions + */ + validatedSelectedOptions: KnockoutBindingHandler; + + /** + * Validates entries that can be keyed or selected. + * Functions in the same way as the ko.bindingHandlers.value binding handler, with the following + * alterations: + *
    + *
  • registers a blur event handler: + *
      + *
    • to display validation messages as entries or selections lose focus
    • + *
    • to reformat successfully parsed textual entries
    • + *
    + *
  • + *
  • + * registers a focus event handler to pause the update of any existing visible validation message + *
  • + *
  • + * registers a key-up event handler which validates the entry as it's being entered; this allows other + * entries that are shown conditionally to be available before the user tabs out of this entry + *
  • + *
+ * @name ko.bindingHandlers.validatedValue + */ + validatedValue: KnockoutBindingHandler; + + /** + * Disables the element when the chosen property or model has failed or is pending validation, enabled + * otherwise. + * @name ko.bindingHandlers.disabledWhenNotValid + */ + disabledWhenNotValid: KnockoutBindingHandler; + + /** + * Disables the element when the chosen property or model has been touched and has failed or is pending + * validation, enabled otherwise.
+ * @name ko.bindingHandlers.disabledWhenTouchedAndNotValid + */ + disabledWhenTouchedAndNotValid: KnockoutBindingHandler; + + /** + * Enables the element when the chosen property or model is applicable, disabled otherwise. + * @name ko.bindingHandlers.enabledWhenApplicable + */ + enabledWhenApplicable: KnockoutBindingHandler; + + /** + * Sets the text of the element to be a formatted representation of the specified property. + * @name ko.bindingHandlers.formattedText + */ + formattedText: KnockoutBindingHandler; + + /** + * Sets CSS classes on the element based on the validation state of the chosen property or model.
+ * The names of the CSS classes used are held in the ko.bindingHandlers.validationCss.classNames object, + * by default they are: + *
    + *
  • failed - if validation failed
  • + *
  • focused - if the element is in focus
  • + *
  • passed - if validation passed
  • + *
  • pending - if validation is pending
  • + *
  • required - if an entry is required
  • + *
  • showMessage - if a validation message should be shown
  • + *
  • touched - set if the model or entry has been "touched"
  • + *
  • untouched - set if the model or entry has not been "touched"
  • + *
+ * @name ko.bindingHandlers.validationCss + */ + validationCss: KnockoutBindingHandler + + /** + * Makes the element behave like a validation message for the chosen property or model: + *
    + *
  • makes the element visible if the value is invalid
  • + *
  • sets the text of the element to be the underlying validation state's message
  • + *
+ * @name ko.bindingHandlers.validationMessage + */ + validationMessage: KnockoutBindingHandler; + + /** + * Sets the text of the element to be the underlying validation state's message. + * @name ko.bindingHandlers.validationMessageText + */ + validationMessageText: KnockoutBindingHandler; + + /** + * Sets the text of the element to be the underlying validation state's name. + * @name ko.bindingHandlers.validationName + */ + validationName: KnockoutBindingHandler; + + /** + * Makes the element visible if the chosen property or model is applicable, invisible otherwise. + * @name ko.bindingHandlers.visibleWhenApplicable + */ + visibleWhenApplicable: KnockoutBindingHandler; + + /** + * Makes the element visible when the entry bound to the chosen property is in focus, invisible otherwise. + * @name ko.bindingHandlers.visibleWhenFocused + */ + visibleWhenFocused: KnockoutBindingHandler; + + /** + * Makes the element visible when the chosen property or model has failed validation, invisible otherwise. + * @name ko.bindingHandlers.visibleWhenInvalid + */ + visibleWhenInvalid: KnockoutBindingHandler; + + /** + * Makes the element visible when the summary for the chosen model is not empty, invisible otherwise. + * @name ko.bindingHandlers.visibleWhenSummaryNotEmpty + */ + visibleWhenSummaryNotEmpty: KnockoutBindingHandler; + + /** + * Makes the element visible if the chosen property or model has been touched, invisible otherwise. + * @name ko.bindingHandlers.visibleWhenTouched + */ + visibleWhenTouched: KnockoutBindingHandler; + + /** + * Makes the element visible if the chosen property or model is untouched, invisible otherwise. + * @name ko.bindingHandlers.visibleWhenUntouched + */ + visibleWhenUntouched: KnockoutBindingHandler; + + /** + * Makes the element visible if the chosen property or model has passed validation. + * @name ko.bindingHandlers.visibleWhenValid + */ + visibleWhenValid: KnockoutBindingHandler; +} + + + +// +// root valerie namespace - static methods +// +declare var valerie: Valerie.Static; + +// additional types for Valerie (all inside this namespace) + +declare module Valerie { + + // + // Static methods on valerie namespace + // + interface Static { + + /** + * Maps a source model to a destination model, including only applicable properties + * @param {Object|Array} sourceModel the source model + * @return {*} the destination model + */ + mapApplicableModel(sourceModel: any): any; + + /** + * Maps a source model to a destination model. + * @param {Object|Array} sourceModel the source model + * @param {valerie.includePropertyCallback} [includeWrappedFunction] a function called before each source model + * property is unwrapped, the result of which determines if the property is included in the destination model + * @param {valerie.includePropertyCallback} [includeUnwrappedFunction] a function called after each source + * model property is unwrapped, the result of which determines if the property is included in the destination model + * @return {*} the destination model + */ + mapModel(sourceModel: any, + includeWrappedFunction?: IncludePropertyCallback, + includeUnwrappedFunction?: IncludePropertyCallback): any; + + /** + * Makes the passed-in model validatable. After invocation the model will have a validation state. + *
fluent + * @param {object|function} model the model to make validatable + * @param {valerie.ModelValidationState.options} [options] the options to use when creating the model's validation + * state + * @return {valerie.ModelValidationState} the validation state belonging to the model + */ + validatableModel(model: any, options?: ValidationOptions): ModelValidationState; + + // Makes the passed-in property validatable. After invocation the property will have a validation state. + // (value should be observable or computed) + validatableProperty(value: T, options?: ValidationOptions): PropertyValidationState; + + // additional namespaces for static methods: + + validationState: ValidationState; + } + + // callback interface (see mapModel above) + interface IncludePropertyCallback { + (value: any, sourceModel: any, index: any): boolean; + } + + // Constructs the validation state for a model, which may comprise of simple properties and sub-models. + interface ModelValidationState { + // ctor + new: (model: any, options?: ModelValidationStateOptions) => ModelValidationState; + + addValidationStates(any): void; + + + model: any; + options?: ModelValidationStateOptions + } + + // Construction options for a model validation state. + interface ModelValidationStateOptions { + applicable(): boolean; + excludeFromSummary: boolean; + failureMessage: string; + name(): string; + paused(): boolean; + } + + // + // PropertyValidationState + // + interface PropertyValidationState { + + // properties: + + // the observable or computed the validation state is for + observableOrComputed: T; + // the options to use when creating the validation state + options: ValidationOptions; + + // fluent methods (can be chanined): + + addRule(IRule): PropertyValidationState; + applicable(value: boolean): PropertyValidationState; + applicable(fn: () => boolean): PropertyValidationState; + currencyMajor(options?: ValidationOptions): PropertyValidationState; + currencyMajorMinor(options?: ValidationOptions): PropertyValidationState; + + date(): PropertyValidationState; + during(earliest: Date, latest: Date, options?: ValidationOptions): PropertyValidationState; // date + date + during(earliest: () => Date, latest: Date, options?: ValidationOptions): PropertyValidationState; // dateFN + date + during(earliest: Date, latest: () => Date, options?: ValidationOptions): PropertyValidationState; // date + dateFN + during(earliest: () => Date, latest: () => Date, options?: ValidationOptions): PropertyValidationState; // dateFN + dateFN + earliest(earliest: Date, options?: ValidationOptions): PropertyValidationState; // date value + earliest(earliest: () => Date, options?: ValidationOptions): PropertyValidationState; // date function + email(): PropertyValidationState; + entryFormat(format: string): PropertyValidationState; + excludeFromSummary(): PropertyValidationState; + expression(regularExpression: RegExp, options?: ValidationOptions): PropertyValidationState; // regex + expression(regularExpressionString: string, options?: ValidationOptions): PropertyValidationState; // regex string + float(options?: ValidationOptions): PropertyValidationState; + integer(options?: ValidationOptions): PropertyValidationState; + latest(latestValueOrFunction: Date, options?: ValidationOptions): PropertyValidationState; + latest(latestValueOrFunction: () => Date, options?: ValidationOptions): PropertyValidationState; + lengthBetween(shortest: number, longest: number, options?: ValidationOptions): PropertyValidationState; + lengthBetween(shortest: number, longest: () => number, options?: ValidationOptions): PropertyValidationState; + lengthBetween(shortest: () => number, longest: number, options?: ValidationOptions): PropertyValidationState; + lengthBetween(shortest: () => number, longest: () => number, options?: ValidationOptions): PropertyValidationState; + matches(permitted: any, options?: ValidationOptions): PropertyValidationState; + matches(permitted: () => any, options?: ValidationOptions): PropertyValidationState; + + maximum(maximum: any, options?: ValidationOptions): PropertyValidationState; + maximum(maximum: () => any, options?: ValidationOptions): PropertyValidationState; + maximumLength(longest: number, options?: ValidationOptions): PropertyValidationState; + maximumLength(longest: () => number, options?: ValidationOptions): PropertyValidationState; + maximumNumberOfItems(maximum: number, options?: ValidationOptions): PropertyValidationState; + maximumNumberOfItems(maximum: () => number, options?: ValidationOptions): PropertyValidationState; + + minimum(minimumValueOrFunction: any, options?: ValidationOptions): PropertyValidationState; + minimumLength(shortest: number, options?: ValidationOptions): PropertyValidationState; + minimumLength(shortest: () => number, options?: ValidationOptions): PropertyValidationState; + minimumNumberOfItems(minimum: number, options?: ValidationOptions): PropertyValidationState; + minimumNumberOfItems(minimum: () => number, options?: ValidationOptions): PropertyValidationState; + + name(value: string): PropertyValidationState; + name(value: () => string): PropertyValidationState; + noneOf(forbiddenValues: any[], options?: ValidationOptions): PropertyValidationState; + noneOf(forbiddenValues: () => any[], options?: ValidationOptions): PropertyValidationState; + + not(forbiddenValueOrFunction: any, options?: ValidationOptions): PropertyValidationState; + number(): PropertyValidationState; + numberOfItems(minimumValueOrFunction: any, maximumValueOrFunction: any, options?: ValidationOptions): PropertyValidationState; + oneOf(permittedValues: any[], options?: ValidationOptions): PropertyValidationState; + oneOf(permittedValues: () => any[], options?: ValidationOptions): PropertyValidationState; + postcode(): PropertyValidationState; + range(minimumValueOrFunction: any, maximumValueOrFunction: any, options?: ValidationOptions): PropertyValidationState; + required(valueOrFunction?: any): PropertyValidationState; + rule(testFunction: () => any): PropertyValidationState; + ruleMessage(failureMessageFormat: string): PropertyValidationState; + string(): PropertyValidationState; + + valueFormat(format: string): PropertyValidationState; + + validateChildProperties(): PropertyValidationState; + + // return original observable + end(): T; + + // other methods: not returning PropertyValidationState + failed(): boolean; + getName(): string; + isApplicable(): boolean; + isRequired(): boolean; + message(): string; + passed(): boolean; + pending(): boolean; + showMessage(): boolean; + touched(): boolean; // get touched state + touched(value: boolean): boolean; // set touched state + result(): ValidationResult; + + } + + interface ValidationResult { + state: any; // the result state + failed: boolean; //true if the activity failed validation + passed: boolean; //true if the activity passed validation + pending: boolean; //true if the activity hasn't yet completed + message: string; //a message from the activity + new (state: any, message?: string); + + //TODO: not added static members/methods + createFailedResult(message: string): ValidationResult; + + } + + interface IRule { + defaultOptions: ValidationOptions; + test(value: any): ValidationResult; + } + + // The interface for a validation state. + interface IValidationState { + failed(): boolean; + getName(): string; + isApplicable(): boolean; + message(): string; + passed(): boolean; + pending(): boolean; + result(): ValidationResult; + touched(value?: boolean): boolean; + } + + interface ValidatableModel { + name: (string) => PropertyValidationState; + + // return original observableArray + end: () => T; + } + + interface ValidationOptions { + applicable? (): any; // the function used to determine if the property is applicable + converter?: IConverter; // the converter used to parse user entries and format display of the property's value + entryFormat?: string; // the string used to format the property's value for display in a user entry + excludeFromSummary?: boolean; // whether any validation failures for this property are excluded from a summary + invalidFailureMessage?: string; // the message shown when the user has entered an invalid value + missingFailureMessage?: string; // the message shown when a value is required but is missing + name?: () => any; // the function used to determine the name of the property; used in failure messages + required?: () => any; // the function used to determine if a value is required + rules?: any; //Valerie.array.; // the chain of rules used to validate the property's value + valueFormat?: string; // the string use to format the property's value for display in a message + } + + // The interface for a converter, a pair of functions: format and parse, which work in tandem on a single type of value. + interface IConverter { + format: (value: any, format?: string) => string; // Formats the given value as a string. + parse: (value: string) => any; //Parses the given string as a particular value type. + } + + // A helper for parsing and formatting numeric values. + interface NumericHelper { + + // Adds thousands separators to the given numeric string. + addThousandsSeparator(numericString: string): string; + + // Formats the given numeric value as a string. + format(value: number, format: string): string; + + // Initialises the helper + init(decimalSeparator: string, + thousandsSeparator: string, + currencySign: string, + currencyMinorUnitPlaces: number): NumericHelper; + + // Informs whether the given numeric string represents a currency value with major units only. + isCurrencyMajor(numericString): boolean; + + // Informs whether the given numeric string represents a currency value with major units and optionally minor units. + isCurrencyMajorMinor(numericString): boolean; + + // Informs whether the given numeric string represents a non-integer numeric value. + isFloat(numericString: string): boolean; + + // Informs whether the given numeric string represents an integer value. + isInteger(numericString: string): boolean; + + // Attempts to parse the given numeric string as a number value. The string is unformatted first. + parse(numericString: string): number; + + // Unformats a numeric string; removes currency signs, thousands separators and normalises decimal separators. + unformat(numericString: string): string; + + } + + + interface ValidationState { + + // Finds and returns the validation states + findIn(model: any, + includeSubModels?: boolean, + recurse?: boolean, + validationStates?: IValidationState[]): IValidationState[]; + + // Gets the validation state for the given model, observable or computed. + getFor(modelOrObservableOrComputed: any): IValidationState; + + // nforms if the given model, observable or computed has a validation state. + has(modelOrObservableOrComputed: any): boolean; + + + // Sets the validation state for the given model, observable or computed. + setFor(modelOrObservableOrComputed: any, state: IValidationState): void; + + } +} + +declare module Valerie.Rules { + + /* + + Todo: add classes in valerie.rules namespace + + */ +} \ No newline at end of file