├── styles ├── default │ ├── form-for.styl │ ├── collection-label.styl │ ├── form-for-field.styl │ ├── type-ahead-field.styl │ ├── field-label.styl │ ├── field-error.styl │ ├── _variables.styl │ ├── submit-button.styl │ ├── tooltip.styl │ ├── checkbox-field.styl │ ├── text-field.styl │ ├── radio-field.styl │ ├── _mixins.styl │ └── select-field.styl └── material │ ├── md-input-container.styl │ ├── md-checkbox.styl │ ├── select-field.styl │ └── field-error.styl ├── .gitignore ├── source ├── module.ts ├── interfaces │ ├── builder │ │ ├── view-schema.ts │ │ └── view-field.ts │ ├── submit-button-wrapper.ts │ ├── validation │ │ ├── validation-error-map.ts │ │ ├── validation-ruleset.ts │ │ ├── validation-rule-boolean.ts │ │ ├── validation-rule-field-type.ts │ │ ├── validation-rule-number.ts │ │ ├── validation-rule-custom.ts │ │ ├── validation-rule-regexp.ts │ │ ├── validation-rule-collection.ts │ │ └── validation-rule.ts │ ├── bindable-collection-wrapper.ts │ ├── field-datum.ts │ ├── custom-validation-function.ts │ ├── bindable-field-wrapper.ts │ └── form-for-scope.ts ├── enums │ ├── builder-field-type.ts │ ├── validation-field-type.ts │ └── validation-failure-type.ts ├── utils │ ├── form-for-guid.ts │ ├── string-util.ts │ ├── form-for-state-helper.ts │ ├── promise-utils.ts │ └── nested-object-helper.ts ├── directives │ ├── aria-manager.ts │ ├── field-error.ts │ ├── collection-label.ts │ ├── submit-button.ts │ ├── field-label.ts │ ├── checkbox-field.ts │ ├── form-for-debounce.ts │ ├── form-for-builder.ts │ ├── radio-field.ts │ └── form-for.ts └── services │ └── field-helper.ts ├── .travis.yml ├── templates ├── bootstrap │ ├── field-error.html │ ├── submit-button.html │ ├── collection-label.html │ ├── select-field │ │ ├── _select.html │ │ └── _multi-select.html │ ├── text-field │ │ ├── _textarea.html │ │ └── _input.html │ ├── field-label.html │ ├── checkbox-field.html │ ├── radio-field.html │ ├── select-field.html │ ├── text-field.html │ └── type-ahead-field.html ├── material │ ├── field-error.html │ ├── field-label.html │ ├── submit-button.html │ ├── collection-label.html │ ├── checkbox-field.html │ ├── radio-field.html │ ├── select-field.html │ ├── type-ahead-field.html │ └── text-field.html └── default │ ├── field-error.html │ ├── collection-label.html │ ├── submit-button.html │ ├── select-field │ ├── _select.html │ └── _multi-select.html │ ├── text-field │ ├── _textarea.html │ └── _input.html │ ├── field-label.html │ ├── checkbox-field.html │ ├── radio-field.html │ ├── select-field.html │ ├── text-field.html │ └── type-ahead-field.html ├── circle.yml ├── dist ├── form-for.material.css ├── form-for.material-templates.js ├── form-for.default-templates.js └── form-for.bootstrap-templates.js ├── tests ├── unit │ └── services │ │ ├── parse.js │ │ ├── string-util.js │ │ ├── promise-utils.js │ │ ├── form-for-state-helper.js │ │ └── nested-object-helper.js └── integration │ ├── test-helper.js │ └── directives │ ├── checkbox-field.js │ └── radio-field.js ├── bower.json ├── protractor.conf.js ├── karma.conf.js ├── LICENSE ├── examples ├── checkbox-field.html ├── radio-field.html ├── select-field.html └── init-test.js ├── README.md ├── package.json ├── gulpfile.js └── CONTRIBUTING.md /styles/default/form-for.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/material/md-input-container.styl: -------------------------------------------------------------------------------- 1 | md-input-container { 2 | } -------------------------------------------------------------------------------- /styles/default/collection-label.styl: -------------------------------------------------------------------------------- 1 | .collection-label { 2 | position: relative; 3 | } 4 | -------------------------------------------------------------------------------- /styles/material/md-checkbox.styl: -------------------------------------------------------------------------------- 1 | md-checkbox { 2 | display: inline-block !important; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.bower 3 | /.idea 4 | /bower_components 5 | /node_modules 6 | /npm-debug.log 7 | -------------------------------------------------------------------------------- /source/module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | angular.module('formFor', []); -------------------------------------------------------------------------------- /styles/default/form-for-field.styl: -------------------------------------------------------------------------------- 1 | .form-for-field { 2 | display: block; 3 | position: relative; 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'iojs' 4 | - 'node' 5 | install: 6 | - npm install 7 | script: 8 | - gulp travis -------------------------------------------------------------------------------- /styles/default/type-ahead-field.styl: -------------------------------------------------------------------------------- 1 | @require '_mixins'; 2 | @require '_variables'; 3 | 4 | type-ahead-field { 5 | display: block; 6 | } -------------------------------------------------------------------------------- /templates/bootstrap/field-error.html: -------------------------------------------------------------------------------- 1 |

5 |

6 | -------------------------------------------------------------------------------- /templates/material/field-error.html: -------------------------------------------------------------------------------- 1 |
6 |
7 | -------------------------------------------------------------------------------- /templates/default/field-error.html: -------------------------------------------------------------------------------- 1 |

5 |

6 | -------------------------------------------------------------------------------- /styles/material/select-field.styl: -------------------------------------------------------------------------------- 1 | select-field { 2 | md-select { 3 | padding: 0; 4 | } 5 | 6 | md-input-container { 7 | label { 8 | display: none; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /templates/material/field-label.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/material/submit-button.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4 | 0.10.28 5 | test: 6 | pre: 7 | - npm start: 8 | background: true 9 | - sleep 5 10 | override: 11 | - npm run unit 12 | - npm run protractor -------------------------------------------------------------------------------- /source/interfaces/builder/view-schema.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Describes the desired view layout for an auto-generated form. 5 | * This schema is a map of field-name to view options. 6 | */ 7 | export interface ViewSchema { 8 | [fieldName:string]:ViewField; 9 | } 10 | } -------------------------------------------------------------------------------- /templates/bootstrap/submit-button.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /templates/material/collection-label.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /styles/material/field-error.styl: -------------------------------------------------------------------------------- 1 | field-error { 2 | display: block; 3 | 4 | // formFor fieldError isn't nested inside of an mdInputContainer and so doesn't inherit the styles. 5 | // These are the relevant ones though... 6 | .text-danger { 7 | font-size: 12px; 8 | line-height: 24px; 9 | color: rgb(244, 67, 54); 10 | } 11 | } -------------------------------------------------------------------------------- /templates/default/collection-label.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /source/interfaces/submit-button-wrapper.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Object containing keys to be observed by the input button. 5 | */ 6 | export interface SubmitButtonWrapper { 7 | 8 | /** 9 | * Button should disable itself if this value becomes true; typically this means the form is being submitted. 10 | */ 11 | disabled:boolean; 12 | }; 13 | }; -------------------------------------------------------------------------------- /source/interfaces/validation/validation-error-map.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Map of field names to error messages describing validation failures. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationErrorMap { 9 | [fieldName:string]:string; 10 | }; 11 | }; -------------------------------------------------------------------------------- /dist/form-for.material.css: -------------------------------------------------------------------------------- 1 | field-error { 2 | display: block; 3 | } 4 | field-error .text-danger { 5 | font-size: 12px; 6 | line-height: 24px; 7 | color: #f44336; 8 | } 9 | 10 | md-checkbox { 11 | display: inline-block !important; 12 | } 13 | 14 | 15 | select-field md-select { 16 | padding: 0; 17 | } 18 | select-field md-input-container label { 19 | display: none; 20 | } 21 | -------------------------------------------------------------------------------- /templates/default/submit-button.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /templates/bootstrap/collection-label.html: -------------------------------------------------------------------------------- 1 |

2 | 6 | 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /templates/bootstrap/select-field/_select.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/enums/builder-field-type.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Input types available for auto-created forms; see {@link FieldView}. 5 | */ 6 | export enum BuilderFieldType { 7 | CHECKBOX = "checkbox", 8 | NUMBER = "number", 9 | PASSWORD = "password", 10 | RADIO = "radio", 11 | SELECT = "select", 12 | TEXT = "text" 13 | } 14 | } -------------------------------------------------------------------------------- /templates/bootstrap/select-field/_multi-select.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/interfaces/validation/validation-ruleset.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Maps one or more attributes to a set of validation rules governing their behavior. 5 | * See {@link ValidationRules}. 6 | * 7 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 8 | */ 9 | export interface ValidationRuleSet { 10 | [fieldName:string]:ValidationRules; 11 | }; 12 | }; -------------------------------------------------------------------------------- /templates/default/select-field/_select.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /templates/material/checkbox-field.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | {{label}} 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /source/enums/validation-field-type.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Constraints that can be applied to a form field. 5 | * These constraints can be combined (e.g. "positive integer"). 6 | */ 7 | export enum ValidationFieldType { 8 | EMAIL = "email", 9 | INTEGER = "integer", 10 | NEGATIVE = "negative", 11 | NON_NEGATIVE = "nonNegative", 12 | NUMBER = "number", 13 | POSITIVE = "positive" 14 | }; 15 | }; -------------------------------------------------------------------------------- /templates/default/select-field/_multi-select.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /templates/default/text-field/_textarea.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /templates/bootstrap/text-field/_textarea.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /styles/default/field-label.styl: -------------------------------------------------------------------------------- 1 | @require '_variables'; 2 | 3 | field-label { 4 | display: inline-block; 5 | } 6 | 7 | .field-label { 8 | position: relative; 9 | cursor: inherit; 10 | font-weight: bold; 11 | user-select: none; 12 | 13 | &:hover { 14 | .form-for-tooltip { 15 | .form-for-tooltip-popover { 16 | display: inline-block; 17 | } 18 | } 19 | } 20 | } 21 | 22 | .field-label-required-label { 23 | color: $color-text-1; 24 | font-style: italic; 25 | font-size: .8em; 26 | } 27 | -------------------------------------------------------------------------------- /tests/unit/services/parse.js: -------------------------------------------------------------------------------- 1 | describe('$parse', function() { 2 | 'use strict'; 3 | 4 | beforeEach(module('formFor')); 5 | 6 | var $parse; 7 | 8 | beforeEach(inject(function ($injector) { 9 | $parse = $injector.get('$parse'); 10 | })); 11 | 12 | describe('StringUtil', function() { 13 | it('should gracefully handle null and empty strings', function() { 14 | var data = {}; 15 | 16 | $parse('names[0]').assign(data, 'value'); 17 | 18 | expect(data.names[0]).toEqual('value'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /templates/default/text-field/_input.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /source/utils/form-for-guid.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * UID generator for formFor input fields. 5 | * @see http://stackoverflow.com/questions/6248666/how-to-generate-short-uid-like-ax4j9z-in-js 6 | * 7 | *

Intended for use only by formFor directive; this class is not exposed to the $injector. 8 | */ 9 | export class FormForGUID { 10 | 11 | /** 12 | * Create a new GUID. 13 | */ 14 | public static create():string { 15 | return ("0000" + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /templates/bootstrap/text-field/_input.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /source/interfaces/validation/validation-rule-boolean.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Associates a boolean validation rule with a custom failure message. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationRuleBoolean { 9 | 10 | /** 11 | * This rule is active. 12 | * If the condition it applies to is not met, the field should be considered invalid. 13 | */ 14 | rule:boolean; 15 | 16 | /** 17 | * Custom error message to be shown for failed validations. 18 | */ 19 | message:string; 20 | }; 21 | }; -------------------------------------------------------------------------------- /source/interfaces/bindable-collection-wrapper.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * A bind-friendly wrapper object describing the state of the collection. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface BindableCollectionWrapper { 9 | 10 | /** 11 | * Header should display the string contained in this field (if one exists); this means the collection is invalid. 12 | */ 13 | error?:string; 14 | 15 | /** 16 | * Header should display a 'required' indicator if this value is true. 17 | */ 18 | required:boolean; 19 | }; 20 | }; -------------------------------------------------------------------------------- /source/interfaces/validation/validation-rule-field-type.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Associates a field-type validation rule with a custom failure message. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationRuleFieldType { 9 | 10 | /** 11 | * The required field type. 12 | * If the condition it applies to is not met, the field should be considered invalid. 13 | */ 14 | rule:ValidationFieldType; 15 | 16 | /** 17 | * Custom error message to be shown for failed validations. 18 | */ 19 | message:string; 20 | }; 21 | }; -------------------------------------------------------------------------------- /source/interfaces/validation/validation-rule-number.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Associates a numeric validation rule with a custom failure message. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationRuleNumber { 9 | 10 | /** 11 | * Describes the numeric constraint to be applied to the associated field. 12 | * If the condition is not met, the field should be considered invalid. 13 | */ 14 | rule:number; 15 | 16 | /** 17 | * Custom error message to be shown for failed validations. 18 | */ 19 | message:string; 20 | }; 21 | }; -------------------------------------------------------------------------------- /source/interfaces/validation/validation-rule-custom.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Associates a custom validation rule with a custom failure message. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationRuleCustom { 9 | 10 | /** 11 | * Custom validation function. 12 | * If this function returns a rejected promise or a falsy value, the field should be considered invalid. 13 | */ 14 | rule:CustomValidationFunction; 15 | 16 | /** 17 | * Custom error message to be shown for failed validations. 18 | */ 19 | message:string; 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /source/interfaces/validation/validation-rule-regexp.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Associates a RegExp validation rule with a custom failure message. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationRuleRegExp { 9 | 10 | /** 11 | * Describes the regular expression condition to be applied to the associated field. 12 | * If the condition is not met, the field should be considered invalid. 13 | */ 14 | rule:RegExp; 15 | 16 | /** 17 | * Custom error message to be shown for failed validations. 18 | */ 19 | message:string; 20 | }; 21 | }; -------------------------------------------------------------------------------- /templates/bootstrap/field-label.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /templates/default/field-label.html: -------------------------------------------------------------------------------- 1 |

?
12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-form-for", 3 | "version": "4.1.10", 4 | "main": [ 5 | "./dist/form-for.css", 6 | "./dist/form-for.js" 7 | ], 8 | "homepage": "https://github.com/bvaughn/angular-form-for", 9 | "authors": [ 10 | "Brian Vaughn " 11 | ], 12 | "description": "Set of Angular directives to simplify creating and validating HTML forms.", 13 | "keywords": [ 14 | "angular", 15 | "form", 16 | "forms", 17 | "crud", 18 | "validation" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | "dependencies": {}, 29 | "devDependencies": {} 30 | } 31 | -------------------------------------------------------------------------------- /source/interfaces/field-datum.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Wrapper object containing using information about a formFor field. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface FieldDatum { 9 | 10 | /** 11 | * Shared between formFor and field directives. 12 | */ 13 | bindableWrapper:BindableFieldWrapper; 14 | 15 | /** 16 | * Uniquely identifies a form field. 17 | */ 18 | fieldName:string; 19 | 20 | /** 21 | * Helper utility class used by formFor and its directives. 22 | */ 23 | formForStateHelper:FormForStateHelper; 24 | 25 | /** 26 | * Callbacks for removing $scope.$watch watchers. 27 | */ 28 | unwatchers:Array; 29 | }; 30 | }; -------------------------------------------------------------------------------- /source/interfaces/validation/validation-rule-collection.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Describes rules for validating a form-field that contains a collection. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export interface ValidationRuleCollection { 9 | 10 | /** 11 | * Rules for validating properties of objects within the current collection. 12 | * See {@link ValidationRules}. 13 | */ 14 | fields?:ValidationRuleSet; 15 | 16 | /** 17 | * The collection must contain no more than this many items. 18 | */ 19 | max?:number|ValidationRuleNumber; 20 | 21 | /** 22 | * The collection must contain at least this many items. 23 | */ 24 | min?:number|ValidationRuleNumber; 25 | }; 26 | }; -------------------------------------------------------------------------------- /source/interfaces/custom-validation-function.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Describes the signature(s) for a custom field validation function. 5 | * 6 | *

This function is passed the following parameters: 7 | *

    8 | *
  • value: The value of the field, as entered into the form 9 | *
  • formData: The entire form data object 10 | *
11 | * 12 | *

This function should return either a truthy/falsy value or a Promise to be resolved/rejected. 13 | * If a Promise is returned, it can be rejected with a custom validation error message. 14 | * 15 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 16 | */ 17 | export interface CustomValidationFunction { 18 | (value:any, formData:any, fieldName:any): boolean|ng.IPromise; 19 | }; 20 | }; -------------------------------------------------------------------------------- /source/enums/validation-failure-type.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Identifies a validation failure type. 5 | */ 6 | export enum ValidationFailureType { 7 | COLLECTION_MAX_SIZE = "COLLECTION_MAX_SIZE", 8 | COLLECTION_MIN_SIZE = "COLLECTION_MIN_SIZE", 9 | CUSTOM = "CUSTOM", 10 | INCREMENT = "INCREMENT", 11 | MAXIMUM = "MAXIMUM", 12 | MAX_LENGTH = "MAX_LENGTH", 13 | MINIMUM = "MINIMUM", 14 | MIN_LENGTH = "MIN_LENGTH", 15 | PATTERN = "PATTERN", 16 | REQUIRED = "REQUIRED_FIELD", 17 | TYPE_EMAIL = "TYPE_EMAIL", 18 | TYPE_INTEGER = "TYPE_INTEGER", 19 | TYPE_NEGATIVE = "TYPE_NEGATIVE", 20 | TYPE_NON_NEGATIVE = "TYPE_NON_NEGATIVE", 21 | TYPE_NUMERIC = "TYPE_NUMERIC", 22 | TYPE_POSITIVE = "TYPE_POSITIVE" 23 | }; 24 | }; -------------------------------------------------------------------------------- /templates/default/checkbox-field.html: -------------------------------------------------------------------------------- 1 |

2 | 5 | 6 | 7 | 16 | 17 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /templates/material/radio-field.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /styles/default/field-error.styl: -------------------------------------------------------------------------------- 1 | @require '_variables'; 2 | 3 | error-field-background-color = rgba(red, .95); 4 | 5 | .field-error { 6 | position: absolute; 7 | top: 100%; 8 | border-radius: 4px; 9 | background-color: red; 10 | background-color: error-field-background-color; 11 | color: white; 12 | padding: 7px 15px; 13 | z-index: 1000; 14 | margin: 0; 15 | pointer-events: none; 16 | 17 | &::before { 18 | content: ''; 19 | border: 7px solid transparent; 20 | position: absolute; 21 | bottom: 100%; 22 | border-bottom-color: red; 23 | border-bottom-color: error-field-background-color; 24 | } 25 | 26 | &:not(.left-aligned) { 27 | right: 3px; 28 | 29 | &::before { 30 | right: 10px; 31 | } 32 | } 33 | 34 | &.left-aligned { 35 | left: 0; 36 | 37 | &::before { 38 | left: 4px; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/directives/aria-manager.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Helper directive for input elements. 5 | * Observes the $scope :model attribute and updates aria-* elements accordingly. 6 | */ 7 | export class AriaManager implements ng.IDirective { 8 | 9 | restrict:string = 'A'; 10 | 11 | /* @ngInject */ 12 | link($scope:ng.IScope, $element:ng.IAugmentedJQuery, $attributes:ng.IAttributes):void { 13 | $scope.$watch('model.uid', function(uid) { 14 | $attributes.$set('ariaDescribedby', uid + '-error'); 15 | $attributes.$set('ariaLabelledby', uid + '-label'); 16 | }); 17 | 18 | $scope.$watch('model.error', function(error) { 19 | $attributes.$set('ariaInvalid', !!error); 20 | }); 21 | } 22 | } 23 | 24 | angular.module('formFor').directive('ariaManager', () => { 25 | return new AriaManager(); 26 | }); 27 | } -------------------------------------------------------------------------------- /templates/bootstrap/checkbox-field.html: -------------------------------------------------------------------------------- 1 |
3 | 4 | 7 | 8 | 9 | 17 | 18 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | allScriptsTimeout: 11000, 3 | 4 | capabilities: { 5 | browserName: 'chrome', 6 | shardTestFiles: true, 7 | maxInstances: 2 8 | }, 9 | 10 | specs: [ 11 | 'tests/integration/**/*.js' 12 | ], 13 | 14 | seleniumAddress: 'http://localhost:4444/wd/hub', 15 | baseUrl: 'http://localhost:8000/examples/', 16 | 17 | framework: 'jasmine2', 18 | rootElement: 'body', 19 | 20 | jasmineNodeOpts: { 21 | showColors: true, // Use colors in the command line report. 22 | defaultTimeoutInterval: 30000 23 | } 24 | }; 25 | 26 | if (process.env.TRAVIS_BUILD_NUMBER) { 27 | config.sauceUser = process.env.SAUCE_USERNAME; 28 | config.sauceKey = process.env.SAUCE_ACCESS_KEY; 29 | config.capabilities['tunnel-identifier'] = process.env.TRAVIS_JOB_NUMBER; 30 | config.capabilities.build = process.env.TRAVIS_BUILD_NUMBER; 31 | } 32 | 33 | exports.config = config; -------------------------------------------------------------------------------- /styles/default/_variables.styl: -------------------------------------------------------------------------------- 1 | $color-fill-1 = #f9f9f9 2 | $color-fill-2 = #EFEFEF 3 | $color-fill-3 = #CCC 4 | $color-fill-4 = #AAA 5 | $color-fill-5 = #999 6 | $color-fill-6 = #666 7 | 8 | $color-text-1 = #848b88 9 | $color-text-2 = #3d3f3e 10 | $color-text-3 = #252726 11 | 12 | light-blue = #ebf6ff 13 | medium-blue = #66afe9 14 | 15 | blue = #0075ba 16 | gray = $color-fill-4 17 | green = #8eb32b 18 | orange = #ea9707 19 | purple = #813793 20 | red = #e35256 21 | teal = #00a99d 22 | yellow = #ead207 23 | 24 | $border-color = $color-fill-4 25 | $border = 1px solid $border-color 26 | $border-radius = 6px 27 | $border-color-focused = medium-blue 28 | $border-color-error = red 29 | $box-shadow-focused = 0 0 4px medium-blue; 30 | $box-shadow-error-focused = 0 0 4px red; 31 | 32 | $padding-small = 5px 33 | 34 | $opacity-disabled = .5 35 | 36 | $transition = all 100ms linear 37 | 38 | $box-shadow = inset 0 1px 1px rgba(0,0,0,.075) 39 | -------------------------------------------------------------------------------- /templates/default/radio-field.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 8 | 9 | 12 | 13 | 14 | 26 |
-------------------------------------------------------------------------------- /styles/default/submit-button.styl: -------------------------------------------------------------------------------- 1 | @import 'nib'; 2 | @require '_variables'; 3 | 4 | submit-field { 5 | display: block; 6 | } 7 | 8 | .submit-button { 9 | user-select: none; 10 | 11 | transition: $transition; 12 | 13 | border-radius: $border-radius; 14 | border-style: none; 15 | background-color: blue; 16 | color: white; 17 | cursor: pointer; 18 | display: inline-block; 19 | font-size: inherit; 20 | font-weight: 400; 21 | line-height: 35px; 22 | outline: 0; 23 | padding: 0 25px; 24 | white-space: nowrap; 25 | 26 | &:not(.disabled):not(:disabled) { 27 | &:hover, &:active, &:focus { 28 | background-color: lighten(blue, 5%); 29 | color: white; 30 | } 31 | } 32 | 33 | &:active { 34 | transition: none; 35 | 36 | box-shadow: inset 0 4px 2px rgba(0,0,0, 0.1); 37 | } 38 | 39 | &.disabled, 40 | &:disabled { 41 | opacity: .5; 42 | cursor: default; 43 | box-shadow: none; 44 | } 45 | } 46 | 47 | .submit-button-icon { 48 | margin-right: 5px; 49 | } 50 | -------------------------------------------------------------------------------- /source/utils/string-util.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Utility for working with strings. 5 | * 6 | *

Intended for use only by formFor directive; this class is not exposed to the $injector. 7 | */ 8 | export class StringUtil { 9 | 10 | /** 11 | * Converts text in common variable formats to humanized form. 12 | * 13 | * @param text Name of variable to be humanized (ex. myVariable, my_variable) 14 | * @returns Humanized string (ex. 'My Variable') 15 | */ 16 | public static humanize(text:string):string { 17 | if (!text) { 18 | return ''; 19 | } 20 | 21 | text = text.replace(/[A-Z]/g, function (match) { 22 | return ' ' + match; 23 | }); 24 | 25 | text = text.replace(/_([a-z])/g, function (match, $1) { 26 | return ' ' + $1.toUpperCase(); 27 | }); 28 | 29 | text = text.replace(/\s+/g, ' '); 30 | text = text.trim(); 31 | text = text.charAt(0).toUpperCase() + text.slice(1); 32 | 33 | return text; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /templates/default/select-field.html: -------------------------------------------------------------------------------- 1 |

2 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 20 | 21 | 24 |
25 | -------------------------------------------------------------------------------- /templates/bootstrap/radio-field.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 15 | 16 | 17 |
18 | 30 |
31 |
32 | -------------------------------------------------------------------------------- /templates/default/text-field.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 24 |
25 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | autoWatch: true, 6 | basePath: '', 7 | frameworks: ['jasmine'], 8 | preprocessors: { 9 | 'templates/**/*.html': 'ng-html2js' 10 | }, 11 | // See https://github.com/karma-runner/karma-ng-html2js-preprocessor 12 | ngHtml2JsPreprocessor: { 13 | }, 14 | files: [ 15 | 'node_modules/jquery/dist/jquery.js', 16 | 'node_modules/angular/angular.js', 17 | 'node_modules/angular-mocks/angular-mocks.js', 18 | 'node_modules/jasmine-object-matchers/dist/jasmine-object-matchers.js', 19 | 'node_modules/jasmine-promise-matchers/dist/jasmine-promise-matchers.js', 20 | 'dist/form-for.js', 21 | 'tests/unit/**/*.js', 22 | 'templates/**/*.html' 23 | ], 24 | exclude: [], 25 | port: 9999, 26 | browsers: [ 27 | 'PhantomJS' 28 | ], 29 | plugins: [ 30 | 'karma-phantomjs-launcher', 31 | 'karma-jasmine', 32 | 'karma-ng-html2js-preprocessor' 33 | ], 34 | singleRun: false, 35 | colors: true, 36 | logLevel: config.LOG_INFO 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Brian Vaughn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /templates/bootstrap/select-field.html: -------------------------------------------------------------------------------- 1 |
4 | 5 | 12 | 13 | 14 | 16 | 17 | 18 | 21 | 22 | 25 | 26 | 29 |
30 | -------------------------------------------------------------------------------- /templates/material/select-field.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 5 | 6 | 12 | 13 | 15 | {{option[labelAttribute]}} 16 | 17 | 18 | 19 | 24 | 25 | 27 | {{option[labelAttribute]}} 28 | 29 | 30 | 31 | 33 | 34 | 35 |
-------------------------------------------------------------------------------- /templates/material/type-ahead-field.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | {{option[labelAttribute]}} 24 | 25 | 26 | 27 | 28 | No matches found for "{{scopeBuster.filter}}". 29 | 30 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /templates/material/text-field.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 18 | 19 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /styles/default/tooltip.styl: -------------------------------------------------------------------------------- 1 | @require '_variables'; 2 | 3 | .form-for-tooltip { 4 | display: inline-flex; 5 | align-items: center; 6 | height: 15px; 7 | width: 15px; 8 | } 9 | 10 | .form-for-tooltip-icon { 11 | display: inline-block; 12 | width: 15px; 13 | height: 15px; 14 | flex: 0 0 15px; 15 | border-radius: 15px; 16 | line-height: 15px; 17 | background-color: $color-text-1; 18 | color: white; 19 | text-align: center; 20 | font-weight: 600; 21 | font-size: 12px; 22 | cursor: default; 23 | 24 | &:hover { 25 | background-color: $color-text-2; 26 | } 27 | } 28 | 29 | .form-for-tooltip-popover { 30 | pointer-events: none; 31 | display: none; 32 | position: relative; 33 | z-index: 2; 34 | flex: 0 0 350px; 35 | width: 350px; 36 | border-radius: $border-radius; 37 | padding: 15px; 38 | margin-left: 15px; 39 | background-color: light-blue; 40 | color: $color-text-3; 41 | border-bottom: 1px solid rgba(0,0,0,.15); 42 | font-weight: 400; 43 | 44 | &:before { 45 | position: absolute; 46 | right: 100%; 47 | top: 50%; 48 | margin-top: -10px; 49 | content: ''; 50 | border: 10px solid transparent; 51 | border-right-color: light-blue; 52 | color: $color-text-3; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /templates/bootstrap/text-field.html: -------------------------------------------------------------------------------- 1 |
3 | 4 | 11 | 12 | 13 | 15 | 16 | 17 |
18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /tests/unit/services/string-util.js: -------------------------------------------------------------------------------- 1 | describe('StringUtil', function() { 2 | 'use strict'; 3 | 4 | beforeEach(module('formFor')); 5 | 6 | describe('StringUtil', function() { 7 | it('should gracefully handle null and empty strings', function() { 8 | expect(formFor.StringUtil.humanize(null)).toEqual(''); 9 | expect(formFor.StringUtil.humanize(undefined)).toEqual(''); 10 | expect(formFor.StringUtil.humanize('')).toEqual(''); 11 | }); 12 | 13 | it('should convert snake-case variables to humanized strings', function() { 14 | expect(formFor.StringUtil.humanize('snake_case')).toEqual('Snake Case'); 15 | expect(formFor.StringUtil.humanize('snake_case_too')).toEqual('Snake Case Too'); 16 | }); 17 | 18 | it('should convert camel-case variables to humanized strings', function() { 19 | expect(formFor.StringUtil.humanize('camelCase')).toEqual('Camel Case'); 20 | expect(formFor.StringUtil.humanize('camelCaseToo')).toEqual('Camel Case Too'); 21 | }); 22 | 23 | it('should not convert already-humanized strings', function() { 24 | expect(formFor.StringUtil.humanize('Word')).toEqual('Word'); 25 | expect(formFor.StringUtil.humanize('Humanized String')).toEqual('Humanized String'); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /styles/default/checkbox-field.styl: -------------------------------------------------------------------------------- 1 | @require '_mixins'; 2 | @require '_variables'; 3 | 4 | checkbox-field { 5 | display: block; 6 | position: relative; 7 | } 8 | 9 | .form-for-field { 10 | > input[type="checkbox"] { 11 | @extend $form-for-hidden-input 12 | 13 | +label { 14 | cursor: pointer; 15 | display: inline-block; 16 | 17 | &:before { 18 | @extend $form-for-toggle-ui 19 | 20 | border-radius: 4px; 21 | content: " "; // Hidden 22 | } 23 | } 24 | 25 | &:focus + label:before { 26 | border-color: $border-color-focused; 27 | box-shadow: $box-shadow-focused; 28 | } 29 | 30 | &:checked + label:before { 31 | content: "\f00c"; 32 | } 33 | 34 | &:disabled { 35 | opacity: 0; 36 | cursor: default; 37 | pointer-events: none; 38 | 39 | +label { 40 | cursor: default; 41 | opacity: $opacity-disabled; 42 | 43 | &:before { 44 | cursor: default; 45 | } 46 | } 47 | } 48 | } 49 | 50 | > input[type="checkbox"]:invalid, 51 | &.invalid > input[type="checkbox"] { 52 | +label:before { 53 | border-color: red; 54 | } 55 | 56 | &:focus + label:before { 57 | box-shadow: $box-shadow-error-focused; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /source/interfaces/bindable-field-wrapper.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Wrapper object for a form-field attribute that exposes field-state to field directives. 5 | * 6 | *

Note that this interface exists for type-checking only; nothing actually implements this interface. 7 | */ 8 | export class BindableFieldWrapper { 9 | 10 | /** 11 | * Input should 2-way bind against this attribute in order to sync data with formFor. 12 | */ 13 | public bindable:any; 14 | 15 | /** 16 | * Input should disable itself if this value becomes true; typically this means the form is being submitted. 17 | */ 18 | public disabled:boolean; 19 | 20 | /** 21 | * Input should display the string contained in this field (if one exists); this means the input value is invalid. 22 | */ 23 | public error:string; 24 | 25 | /** 26 | * Unique identifier for field; can be used for WCAG compatibility (aria-* attributes). 27 | */ 28 | public pristine:boolean; 29 | 30 | /** 31 | * Input should display a 'required' indicator if this value is true. 32 | */ 33 | public required:boolean; 34 | 35 | /** 36 | * Field has been modified since initialization (or last reset via resetField/resetFields). 37 | */ 38 | public uid:string; 39 | }; 40 | }; -------------------------------------------------------------------------------- /styles/default/text-field.styl: -------------------------------------------------------------------------------- 1 | @require '_mixins'; 2 | @require '_variables'; 3 | 4 | text-field { 5 | display: block; 6 | } 7 | 8 | .form-for-field { 9 | input, 10 | textarea { 11 | 12 | @extend $form-for-input 13 | 14 | box-shadow: inset 0 1px 4px rgba(0,0,0, 0.15); 15 | 16 | &:focus { 17 | border-color: $border-color-focused; 18 | box-shadow: $box-shadow-focused; 19 | } 20 | 21 | &:disabled { 22 | opacity: $opacity-disabled; 23 | pointer-events: none; 24 | } 25 | } 26 | 27 | &.invalid input, 28 | &.invalid textarea, 29 | input:invalid 30 | textarea:invalid { 31 | border-color: red; 32 | 33 | &:focus { 34 | box-shadow: $box-shadow-error-focused; 35 | } 36 | } 37 | 38 | &.with-icon-before { 39 | input, 40 | textarea { 41 | padding-left: 40px; 42 | } 43 | } 44 | 45 | &.with-icon-after { 46 | input, 47 | textarea { 48 | padding-right: 40px; 49 | } 50 | } 51 | } 52 | 53 | .form-for-input-icon-left, 54 | .form-for-input-icon-right { 55 | position: absolute; 56 | bottom: 17px; 57 | width: 40px; 58 | text-align: center; 59 | line-height: 1em; 60 | margin-bottom: -.5em; 61 | } 62 | 63 | .form-for-input-icon-left { 64 | left: 0; 65 | } 66 | 67 | .form-for-input-icon-right { 68 | right: 0; 69 | } 70 | -------------------------------------------------------------------------------- /source/directives/field-error.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | interface FieldErrorScope extends ng.IScope { 4 | 5 | /** 6 | * Error messages to display (or null if field is valid OR pristine) 7 | */ 8 | error?:string; 9 | 10 | /** 11 | * Apply additional 'left-aligned' class to error message (useful for checkbox and radio items) 12 | */ 13 | leftAligned?:boolean; 14 | 15 | /** 16 | * Optional UID for HTML element containing the error message string 17 | */ 18 | uid?:string; 19 | } 20 | 21 | /** 22 | * Displays a standard formFor field validation error message. 23 | * 24 | * @example 25 | * // To display a field error: 26 | * 27 | * 28 | */ 29 | export class FieldErrorDirective implements ng.IDirective { 30 | 31 | restrict:string = 'EA'; 32 | templateUrl:string = ($element, $attributes) => { 33 | return $attributes['template'] || 'form-for/templates/field-error.html'; 34 | }; 35 | 36 | scope:any = { 37 | error: '=', 38 | leftAligned: '@?', 39 | uid: '@' 40 | }; 41 | 42 | /* @ngInject */ 43 | link($scope:FieldErrorScope):void { 44 | } 45 | } 46 | 47 | angular.module('formFor').directive('fieldError', () => { 48 | return new FieldErrorDirective(); 49 | }); 50 | } -------------------------------------------------------------------------------- /examples/checkbox-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 |

24 | 26 | 27 | 28 | 30 | 31 | 32 | 35 | 36 | 37 | 40 | 41 | 42 | 44 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /styles/default/radio-field.styl: -------------------------------------------------------------------------------- 1 | @require '_mixins'; 2 | @require '_variables'; 3 | 4 | radio-field { 5 | display: block; 6 | position: relative; 7 | } 8 | 9 | .form-for-field { 10 | > label { 11 | display: block; 12 | 13 | > input[type="radio"] { 14 | @extend $form-for-hidden-input 15 | 16 | +span { 17 | cursor: pointer; 18 | display: inline-block; 19 | 20 | &:before { 21 | @extend $form-for-toggle-ui 22 | 23 | border-radius: 10px; 24 | content: " "; // Hidden 25 | font-size: 11px; 26 | } 27 | } 28 | 29 | &:focus + span:before { 30 | border-color: $border-color-focused; 31 | box-shadow: $box-shadow-focused; 32 | } 33 | 34 | &:checked + span:before { 35 | content: "\f111"; 36 | } 37 | 38 | &:disabled { 39 | opacity: 0; 40 | cursor: default; 41 | pointer-events: none; 42 | 43 | +span { 44 | cursor: default; 45 | opacity: $opacity-disabled; 46 | 47 | &:before { 48 | cursor: default; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | > label > input[type="radio"]:invalid, 56 | &.invalid > label > input[type="radio"] { 57 | +span:before { 58 | border-color: red; 59 | } 60 | 61 | &:focus + span:before { 62 | box-shadow: $box-shadow-error-focused; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /examples/radio-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 27 | 28 |
29 | 32 | 33 | 34 | 38 | 39 | 40 | 44 | 45 | 46 | 49 | 50 |
51 | 52 | -------------------------------------------------------------------------------- /templates/default/type-ahead-field.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 | 12 | 13 | 14 | 26 | 27 |
    28 |
  • 34 |
  • 35 |
36 | 37 | 39 |
40 | -------------------------------------------------------------------------------- /tests/integration/test-helper.js: -------------------------------------------------------------------------------- 1 | exports.assertElementIsClickable = function(element, opt_timeout) { 2 | var promise = protractor.ExpectedConditions.elementToBeClickable(element); 3 | 4 | return browser.wait(promise, opt_timeout || 100); 5 | }; 6 | 7 | exports.assertElementIsNotClickable = function(element, opt_timeout) { 8 | return exports.assertElementIsClickable(element, opt_timeout).then( 9 | function() { 10 | throw Error('Element should not be clickable'); 11 | }, 12 | function() { 13 | // An element that is not clickable is what we want 14 | }); 15 | }; 16 | 17 | exports.assertFormDataValue = function(fieldName, expectedValue) { 18 | expect(exports.getFormDataValue(fieldName)).toBe(expectedValue); 19 | }; 20 | 21 | exports.assertIsDisplayed = function(element, opt_timeout) { 22 | browser.wait(function () { 23 | return element.isPresent() && 24 | element.isDisplayed(); 25 | }, opt_timeout || 100); 26 | }; 27 | 28 | exports.assertIsNotDisplayed = function(element) { 29 | return element.isDisplayed().then( 30 | function(isDisplayed) { 31 | if (isDisplayed) { 32 | throw Error('Element should not be displayed'); 33 | } 34 | }, 35 | function() { 36 | // An element not present in the DOM is not displayed (which is okay) 37 | }); 38 | }; 39 | 40 | exports.doMouseOver = function(element) { 41 | browser.actions().mouseMove(element).perform(); 42 | }; 43 | 44 | exports.getFormDataValue = function(fieldName) { 45 | return element(by.css('form')).evaluate('formData.' + fieldName); 46 | }; 47 | 48 | exports.goToPage = function(url) { 49 | browser.driver.get(url); 50 | browser.driver.wait(browser.driver.isElementPresent(by.id('form')), 5000); 51 | }; -------------------------------------------------------------------------------- /templates/bootstrap/type-ahead-field.html: -------------------------------------------------------------------------------- 1 |
4 | 5 | 12 | 13 | 14 | 16 | 17 | 18 | 19 |
20 | 33 | 34 | 44 | 45 | 46 | 47 | 48 |
49 |
50 | -------------------------------------------------------------------------------- /examples/select-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 29 | 30 |
31 | 35 | 36 | 37 | 41 | 42 | 43 | 47 | 48 | 49 | 53 | 54 | 55 | 59 | 60 |
61 | 62 | -------------------------------------------------------------------------------- /styles/default/_mixins.styl: -------------------------------------------------------------------------------- 1 | @require '_variables'; 2 | 3 | /****** Vender prefixes ******/ 4 | 5 | placeholder-color($color) { 6 | &::-webkit-input-placeholder { 7 | color: $color; 8 | } 9 | &:-moz-placeholder { 10 | /* FF 4-18 */ 11 | color: $color; 12 | } 13 | &::-moz-placeholder { 14 | /* FF 19+ */ 15 | color: $color; 16 | } 17 | &:-ms-input-placeholder { 18 | /* IE 10+ */ 19 | color: $color; 20 | } 21 | } 22 | 23 | user-select(val) { 24 | -ms-user-select: val; 25 | -moz-user-select: val; 26 | -webkit-user-select: val; 27 | } 28 | 29 | appearance(val) { 30 | -ms-appearance: val; 31 | -moz-appearance: val; 32 | -webkit-appearance: val; 33 | } 34 | 35 | /****** Shared base classes ******/ 36 | 37 | $form-for-input { 38 | display: block; 39 | box-sizing: border-box; 40 | width: 100%; 41 | line-height: 1.5; 42 | padding: 6px 9px; 43 | 44 | font-family: inherit; 45 | font-size: inherit; 46 | text-align: left; 47 | color: inherit; 48 | 49 | border: $border; 50 | border-radius: $border-radius; 51 | 52 | appearance: none; 53 | outline: none; 54 | } 55 | 56 | // Should be hidden from user but remain tabbable and clickable 57 | // Note that display:none and visibility:hidden prevent tab/mouse interaction 58 | $form-for-hidden-input { 59 | position: absolute; 60 | opacity: 0; // Hidden but clickable 61 | z-index: 2; // Float above the styled, faux-input so mouse clicks will toggle selected state 62 | width: 20px; 63 | height: 20px; 64 | cursor: pointer; 65 | margin: 0; 66 | } 67 | 68 | $form-for-toggle-ui { 69 | position: absolute; 70 | height: 20px; 71 | width: 20px; 72 | top: 0; 73 | left: 0; 74 | width: 19px; 75 | padding-left: 1px; // Visually centers better 76 | line-height: 19px; 77 | text-align: center; 78 | vertical-align: middle; 79 | display: inline-block; 80 | background-image: linear-gradient(to top, $color-fill-1, white); 81 | border: $border; 82 | position: relative; 83 | margin-right: 5px; 84 | cursor: pointer; 85 | vertical-align: middle; 86 | font-family: FontAwesome; 87 | } 88 | -------------------------------------------------------------------------------- /source/directives/collection-label.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * CheckboxField $scope. 5 | */ 6 | interface CollectionLabelScope extends ng.IScope { 7 | 8 | /** 9 | * Name of collection attribute. 10 | */ 11 | attribute?:string; 12 | 13 | /** 14 | * Bindable label for template to display. 15 | */ 16 | bindableLabel?:string; 17 | 18 | /** 19 | * Optional help tooltip to display on hover. 20 | * By default this makes use of the Angular Bootstrap tooltip directive and the Font Awesome icon set. 21 | */ 22 | help?:string; 23 | 24 | /** 25 | * Incoming (user-specified) label. 26 | * This value is passed along to 'bindableLabel' to be consumed by the associated template. 27 | * This field is allowed to contain markup. 28 | */ 29 | label?:string; 30 | 31 | /** 32 | * Shared between formFor and CollectionLabel directives. 33 | */ 34 | model?:BindableCollectionWrapper; 35 | } 36 | 37 | var $sce_:ng.ISCEService; 38 | 39 | /** 40 | * Header label for collections. 41 | * This component displays header text as well as collection validation errors. 42 | * 43 | * @example 44 | * // To display a simple collection header: 45 | * 46 | * 47 | * 48 | * @param $sce $injector-supplied $sce service 49 | */ 50 | export class CollectionLabelDirective implements ng.IDirective { 51 | 52 | require:string = '^formFor'; 53 | restrict:string = 'EA'; 54 | templateUrl:string = ($element, $attributes) => { 55 | return $attributes['template'] || 'form-for/templates/collection-label.html'; 56 | }; 57 | 58 | scope:any = { 59 | attribute: '@', 60 | help: '@?', 61 | label: '@' 62 | }; 63 | 64 | /* @ngInject */ 65 | constructor($sce:ng.ISCEService) { 66 | $sce_ = $sce; 67 | } 68 | 69 | /* @ngInject */ 70 | link($scope:CollectionLabelScope, 71 | $element:ng.IAugmentedJQuery, 72 | $attributes:ng.IAttributes, 73 | formForController:FormForController):void { 74 | 75 | $scope.$watch('label', function(value) { 76 | $scope.bindableLabel = $sce_.trustAsHtml(value); 77 | }); 78 | 79 | $scope.model = formForController.registerCollectionLabel($scope.attribute); 80 | } 81 | } 82 | 83 | angular.module('formFor').directive('collectionLabel', ($sce) => { 84 | return new CollectionLabelDirective($sce); 85 | }); 86 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer actively maintained. 2 | 3 | Issue reports and pull requests will probably not be attended. 4 | 5 | I am no longer working with Angular on a regular basis. My day job has switched to React and it's too much effort to context switch between the frameworks. Given the upcoming Angular 2.0 release it seems like a reasonable time to end support for this library. 6 | 7 | Thanks to all of you for using, supporting, and contributing to this project. 8 | 9 | # angular-form-for [![Build Status](https://travis-ci.org/bvaughn/angular-form-for.svg)](https://travis-ci.org/bvaughn/angular-form-for) [![Join the chat at https://gitter.im/bvaughn/angular-form-for](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bvaughn/angular-form-for?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 10 | 11 | *formFor* is a quick and easy way to declare complex HTML forms with client and server-side validations. 12 | You can generate a complete form with as little markup as this: 13 | 14 | ```html 15 |
16 | ``` 17 | 18 | But that's not all! *formFor* is incredibly flexible, offering a wide range of configuration options. 19 | Check out the official website to learn more: 20 | [http://bvaughn.github.io/angular-form-for/](http://bvaughn.github.io/angular-form-for/) 21 | 22 | ## Compatibility and Dependencies 23 | 24 | *formFor* is compatible with Angular Angular 1.2 and newer. It does not require any third party libraries such as jQuery, lodash, or underscore. 25 | 26 | ## Installation 27 | 28 | You can install this plugin with either [Bower](http://bower.io/) or [NPM](https://www.npmjs.org/): 29 | 30 | ```shell 31 | bower install angular-form-for --save-dev 32 | npm install angular-form-for --save-dev 33 | ``` 34 | 35 | This will download an `angular-form-for` folder into your bower/node components directory. Inside of that folder there will be a `dist` folder with the *formFor* JavaScript and CSS files. By default *formFor* is compatible with [Bootstrap](getbootstrap.com) 3.2.x styles. A separate, *formFor* only CSS stylehseet is included for those not using Bootstrap. 36 | 37 | Lastly just include the *formFor* module in your Angular application like so: 38 | 39 | ```js 40 | angular.module('myAngularApp', ['formFor']); 41 | ``` 42 | 43 | ## Contributions 44 | 45 | Interested in contributing? Check out the [contribution guidelines](CONTRIBUTING.md)! 46 | 47 | ## License 48 | 49 | Copyright (c) 2014 Brian Vaughn. Licensed under the MIT license. 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-form-for", 3 | "version": "4.1.12", 4 | "description": "Set of Angular directives to simplify creating and validating HTML forms.", 5 | "keywords": [ 6 | "angular", 7 | "form", 8 | "forms", 9 | "crud", 10 | "validation" 11 | ], 12 | "main": "gulpfile.js", 13 | "directories": { 14 | "test": "tests" 15 | }, 16 | "dependencies": {}, 17 | "devDependencies": { 18 | "angular": "~1.3.15", 19 | "angular-animate": "~1.3.15", 20 | "angular-aria": "~1.3.15", 21 | "angular-bootstrap-npm": "~0.12.3", 22 | "angular-material": "0.10.0", 23 | "angular-mocks": "~1.3.15", 24 | "angular-route": "^1.3.15", 25 | "angular-ui-router": "^0.2.13", 26 | "autoprefixer-stylus": "~0.5.0", 27 | "bootstrap": "^3.3.4", 28 | "doxx": "~1.2.5", 29 | "font-awesome": "^4.3.0", 30 | "gulp": "^3.9.0", 31 | "gulp-angular-protractor": "0.0.2", 32 | "gulp-angular-templatecache": "~1.5.0", 33 | "gulp-clean": "~0.3.1", 34 | "gulp-closure-deps": "~0.4.0", 35 | "gulp-concat": "~2.5.2", 36 | "gulp-doxx": "0.0.4", 37 | "gulp-karma": "0.0.4", 38 | "gulp-ng-annotate": "~0.5.2", 39 | "gulp-rename": "~1.2.0", 40 | "gulp-replace": "^0.5.3", 41 | "gulp-shell": "~0.3.0", 42 | "gulp-stylus": "~2.0.1", 43 | "gulp-tsc": "~0.9.2", 44 | "gulp-uglify": "~1.1.0", 45 | "gulp-umd": "~0.1.3", 46 | "html2js": "~0.1.1", 47 | "jasmine": "2.1.1", 48 | "jasmine-object-matchers": "0.0.3", 49 | "jasmine-promise-matchers": "0.0.5", 50 | "jquery": "~2.1.3", 51 | "karma": "~0.12.31", 52 | "karma-jasmine": "~0.3.5", 53 | "karma-jasmine-ajax": "~0.1.11", 54 | "karma-ng-html2js-preprocessor": "~0.1.2", 55 | "karma-phantomjs-launcher": "~0.1.4", 56 | "nib": "~1.1.0", 57 | "phantomjs": "~1.9.13", 58 | "protractor": "^2.0.0", 59 | "replace": "^0.3.0", 60 | "run-sequence": "~1.0.2", 61 | "uglify-js": "~2.4.17", 62 | "uglify-js2": "~2.1.11" 63 | }, 64 | "scripts": { 65 | "prestart": "npm install", 66 | "start": "http-server -p 8000", 67 | "unit": "gulp test:unit", 68 | "update-webdriver": "webdriver-manager update", 69 | "preprotractor": "npm run update-webdriver", 70 | "protractor": "protractor protractor.conf.js" 71 | }, 72 | "repository": { 73 | "type": "git", 74 | "url": "https://github.com/bvaughn/angular-form-for.git" 75 | }, 76 | "author": "Brian Vaughn ", 77 | "license": "MIT", 78 | "bugs": { 79 | "url": "https://github.com/bvaughn/angular-form-for/issues" 80 | }, 81 | "homepage": "https://github.com/bvaughn/angular-form-for" 82 | } 83 | -------------------------------------------------------------------------------- /source/interfaces/builder/view-field.ts: -------------------------------------------------------------------------------- 1 | module formFor { 2 | 3 | /** 4 | * Describes the expected input UI for an individual model field. 5 | */ 6 | export interface ViewField { 7 | 8 | /** 9 | * Attribute name within form data object (e.g. "username" within {username: "John Doe"}). 10 | * This is a convenience attribute added by Forms JS based on the map key in {@link ViewSchema}. 11 | * @private 12 | */ 13 | key_:string; 14 | 15 | /** 16 | * An empty/blank selection should be allowed. 17 | * This property is only supported for {@link BuilderFieldType.SELECT} inputs. 18 | */ 19 | allowBlank?:boolean; 20 | 21 | /** 22 | * Enable filtering of list via a text input at the top of the drop-down. 23 | * This property is only supported for {@link BuilderFieldType.SELECT} inputs. 24 | */ 25 | enableFiltering?:boolean; 26 | 27 | /** 28 | * Optional help text providing additional context to users. 29 | */ 30 | help?:string; 31 | 32 | /** 33 | * Input type used by this field. 34 | * This is a required field. 35 | */ 36 | inputType?:BuilderFieldType; 37 | 38 | /** 39 | * Field