diff --git a/.gitattributes b/.gitattributes
index 176a458f..d0c0c4c1 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
-* text=auto
+* text eol=lf
+*.png binary
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..8a0872fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,27 @@
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..42cd724b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,30 @@
+
+
+## What
+
+
+
+## Why
+
+
+
+## How
+
+
+
+For issue #
+
+Checklist:
+
+* [ ] Follows the commit message [conventions](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)
+* [ ] Is [rebased with master](https://egghead.io/lessons/javascript-how-to-rebase-a-git-pull-request-branch?series=how-to-contribute-to-an-open-source-project-on-github)
+* [ ] Is [only one (maybe two) commits](https://egghead.io/lessons/javascript-how-to-squash-multiple-git-commits)
+
diff --git a/.travis.yml b/.travis.yml
index 94f38f39..94742a78 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,16 +9,16 @@ branches:
notifications:
email: false
node_js:
- - iojs
+ - 4.2
before_install:
- - npm i -g npm@^2.0.0
+ - npm i -g npm@^3.0.0
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
before_script:
- npm prune
script:
- - npm run test:ci
+ - npm run eslint
+ - npm run test
- npm run check-coverage
after_success:
- npm run report-coverage
- - npm run semantic-release
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c283fc96..61473334 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,7 +29,16 @@ with a link to a jsbin that demonstrates the issue with [issue.angular-formly.co
## Pull Requests
-[Watch video](https://www.youtube.com/watch?v=QOchwBm9W-g&list=PLV5CVI1eNcJi7lVVIuNyRhEuck1Z007BH&index=1)
+**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
+
+‼️‼️‼️ 👉**Please follow our commit message conventions** even if you're making a small change! This repository follows the
+[How to Write an Open Source JavaScript Library](https://egghead.io/series/how-to-write-an-open-source-javascript-library)
+series on egghead.io (by yours truly). See
+[this lesson](https://egghead.io/lessons/javascript-how-to-write-a-javascript-library-writing-conventional-commits-with-commitizen?series=how-to-write-an-open-source-javascript-library)
+and [this repo](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)
+to learn more about the commit message conventions.
+
+[Watch video](https://www.youtube.com/watch?v=QOchwBm9W-g&list=PLV5CVI1eNcJi7lVVIuNyRhEuck1Z007BH&index=1) (slightly out of date)
If you would like to add functionality, please submit [an issue](https://github.com/formly-js/angular-formly/issues)
first to make sure it's a direction we want to take.
@@ -45,7 +54,7 @@ Please do the following:
2. run `npm start` (if you're on a windows machine, see [this issue](https://github.com/formly-js/angular-formly/issues/305))
3. write tests & code in ES6 goodness :-)
4. run `git add src/`
-5. run `npm run commit` and follow the prompt (this ensures that your commit message follows [our conventions](https://github.com/ajoslin/conventional-changelog/blob/master/conventions/angular.md)).
+5. run `npm run commit` and follow the prompt (this ensures that your commit message follows [our conventions](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)).
6. notice that there's a pre-commit hook that runs to ensure tests pass and coverage doesn't drop to prevent the build from breaking :-)
7. push your changes
8. create a PR with a link to the original issue
diff --git a/README.md b/README.md
index 13ce77c9..4b752dba 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
## [angular-formly](http://docs.angular-formly.com)
+[THIS PROJECT NEEDS A MAINTAINER](https://github.com/formly-js/angular-formly/issues/638)
+
Status:
[](https://www.npmjs.org/package/angular-formly)
[](http://npm-stat.com/charts.html?package=angular-formly&from=2015-01-01)
@@ -14,6 +16,9 @@ Links:
[](https://egghead.io/playlists/advanced-angular-forms-with-angular-formly)
[](https://gitter.im/formly-js/angular-formly?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://github.com/formly-js/angular-formly/releases)
+[](http://makeapullrequest.com)
+
+
angular-formly is an AngularJS module which has a directive to help customize and render JavaScript/JSON configured forms.
The `formly-form` directive and the `formlyConfig` service are very powerful and bring unmatched maintainability to your
@@ -30,6 +35,9 @@ application's forms.
From there, it's just JavaScript. Allowing for DRY, maintainable, reusable forms.
+> **IMPORTANT:** This is the formly project for AngularJS (v1.x). If you're looking for an **Angular (v2+) alternative**, take a look at the [ngx-formly](https://github.com/formly-js/ngx-formly) project.
+
+
## [Learning](http://learn.angular-formly.com)
### Egghead.io Lessons
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 00000000..a88f54cb
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,12 @@
+const gulp = require('gulp')
+const replace = require('gulp-replace')
+
+// bump npm package version into package.js
+gulp.task('meteor', function() {
+ const pkg = require('./package.json')
+ const versionRegex = /(version\:\s*\')([^\']+)\'/gi
+
+ return gulp.src('package.js')
+ .pipe(replace(versionRegex, '$1' + pkg.version + "'"))
+ .pipe(gulp.dest('./'))
+})
diff --git a/other/karma.conf.es6.js b/other/karma.conf.es6.js
index ee894ff5..a37cf374 100644
--- a/other/karma.conf.es6.js
+++ b/other/karma.conf.es6.js
@@ -1,4 +1,5 @@
/* eslint-env node */
+require('argv-set-env')();
const path = require('path');
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
@@ -19,7 +20,7 @@ module.exports = function(config) {
basePath: './',
frameworks: ['sinon-chai', 'chai', 'mocha', 'sinon'],
files: [
- 'node_modules/lodash/index.js',
+ 'node_modules/lodash/lodash.js',
'node_modules/api-check/dist/api-check.js',
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
@@ -53,4 +54,3 @@ function getReporters() {
}
return reps;
}
-
diff --git a/other/webpack.config.es6.js b/other/webpack.config.es6.js
index 80f6814c..ceaeab2d 100644
--- a/other/webpack.config.es6.js
+++ b/other/webpack.config.es6.js
@@ -1,4 +1,5 @@
/* eslint-env node */
+require('argv-set-env')();
const packageJson = require('../package.json');
const here = require('path-here');
@@ -95,7 +96,8 @@ function getProdConfig() {
plugins: _.union(getCommonPlugins(), [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
- new webpack.optimize.AggressiveMergingPlugin()
+ new webpack.optimize.AggressiveMergingPlugin(),
+ new webpack.optimize.UglifyJsPlugin()
])
};
}
@@ -103,7 +105,6 @@ function getProdConfig() {
function getTestConfig() {
const coverage = process.env.COVERAGE === 'true';
const ci = process.env.CI === 'true';
- console.log(process.env.CI);
return {
entry: './index.test.js',
module: {
diff --git a/package.js b/package.js
new file mode 100644
index 00000000..e8510962
--- /dev/null
+++ b/package.js
@@ -0,0 +1,23 @@
+/* global Package:false */
+// package metadata file for AtmosphereJS
+
+try {
+ Package.describe({
+ name: 'formly:angular-formly',
+ summary: 'angular-formly (official): forms for AngularJS',
+ version: '0.0.0-semantically-released.0',
+ git: 'https://github.com/formly-js/angular-formly.git',
+ })
+
+ Package.onUse(function(api) {
+ api.versionsFrom(['METEOR@1.0'])
+ // api-check
+ api.use('wieldo:api-check@7.5.5')
+ api.imply('wieldo:api-check')
+ // angular
+ api.use('angular:angular@1.4.0')
+ api.addFiles('dist/formly.js', 'client')
+ })
+} catch (e) {
+ //
+}
diff --git a/package.json b/package.json
index d104c404..890d705f 100644
--- a/package.json
+++ b/package.json
@@ -24,25 +24,29 @@
"main": "dist/formly.js",
"license": "MIT",
"scripts": {
- "build:dist": "cross-env NODE_ENV=development webpack --progress --colors",
- "build:prod": "cross-env NODE_ENV=production webpack --progress --colors",
+ "build:dist": "webpack --progress --colors --set-env-NODE_ENV=development",
+ "build:prod": "webpack --progress --colors --set-env-NODE_ENV=production",
"build": "npm run build:dist & npm run build:prod",
- "test": "cross-env COVERAGE=true NODE_ENV=test karma start --single-run",
- "test:ci": "CI=true COVERAGE=true NODE_ENV=test karma start --single-run",
- "test:watch": "cross-env COVERAGE=true NODE_ENV=test karma start",
- "test:debug": "cross-env NODE_ENV=test karma start --browsers Chrome",
- "start:mac": "npm run test:mac",
+ "eslint:test": "eslint -c other/test.eslintrc --ignore-pattern **/*.{test,mock}.js src/",
+ "eslint:src": "eslint -c other/src.eslintrc --ignore-pattern !**/*.{test,mock}.js src/",
+ "eslint": "npm run eslint:test -s && npm run eslint:src -s",
+ "test": "karma start --single-run --set-env-COVERAGE=true --set-env-NODE_ENV=test",
+ "test:watch": "karma start --set-env-COVERAGE=true --set-env-NODE_ENV=test",
+ "test:debug": "karma start --browsers Chrome --set-env-NODE_ENV=test",
"start": "npm run test:watch",
"check-coverage": "istanbul check-coverage --statements 93 --branches 89 --functions 92 --lines 92",
"report-coverage": "cat ./coverage/lcov.info | node_modules/.bin/codecov",
"commit": "git-cz",
- "prepublish": "npm run build",
- "postpublish": "publish-latest --user-email kent+formly-bot@doddsfamily.us --user-name formly-bot",
- "semantic-release": "semantic-release pre && npm publish && semantic-release post"
+ "publish-latest": "publish-latest --user-email kent+formly-bot@doddsfamily.us --user-name formly-bot",
+ "meteor": "gulp meteor",
+ "semantic-release": "semantic-release pre && npm run build && npm run meteor && npm publish && npm run publish-latest && semantic-release post"
},
"config": {
"ghooks": {
- "commit-msg": "./node_modules/.bin/validate-commit-msg && npm t && npm run check-coverage"
+ "commit-msg": "validate-commit-msg && npm run eslint && npm t && npm run check-coverage"
+ },
+ "commitizen": {
+ "path": "node_modules/cz-conventional-changelog"
}
},
"description": "AngularJS directive which takes JSON representing a form and renders to HTML",
@@ -51,61 +55,60 @@
"api-check": "^7.0.0"
},
"devDependencies": {
- "angular": "1.4.7",
- "angular-mocks": "1.4.7",
- "api-check": "7.5.3",
+ "angular": "1.5.0",
+ "angular-mocks": "1.5.0",
+ "api-check": "7.5.5",
+ "argv-set-env": "1.0.1",
"babel": "5.8.23",
"babel-eslint": "4.1.3",
"babel-loader": "5.3.2",
- "chai": "3.3.0",
+ "chai": "3.5.0",
"codecov.io": "0.1.6",
- "commitizen": "1.0.5",
- "cracks": "3.1.1",
- "cross-env": "1.0.1",
- "cz-conventional-changelog": "1.1.4",
+ "commitizen": "2.7.2",
+ "cracks": "3.1.2",
+ "cz-conventional-changelog": "1.1.5",
"deindent": "0.1.0",
- "eslint": "1.6.0",
- "eslint-config-kentcdodds": "4.0.1",
- "eslint-loader": "1.0.0",
+ "eslint": "1.7.3",
+ "eslint-config-kentcdodds": "5.0.0",
+ "eslint-loader": "1.1.0",
"eslint-plugin-mocha": "1.0.0",
- "ghooks": "0.3.2",
- "http-server": "0.8.5",
+ "ghooks": "1.0.3",
+ "gulp": "3.9.1",
+ "gulp-replace": "0.5.4",
+ "http-server": "0.9.0",
"isparta": "3.1.0",
"isparta-loader": "1.0.0",
- "istanbul": "0.3.22",
- "karma": "0.13.10",
+ "istanbul": "0.4.2",
+ "karma": "0.13.22",
"karma-chai": "0.1.0",
- "karma-chrome-launcher": "0.2.0",
- "karma-coverage": "0.5.2",
- "karma-firefox-launcher": "0.1.6",
- "karma-mocha": "0.2.0",
+ "karma-chrome-launcher": "0.2.2",
+ "karma-coverage": "0.5.5",
+ "karma-firefox-launcher": "0.1.7",
+ "karma-mocha": "0.2.2",
"karma-sinon": "1.0.4",
- "karma-sinon-chai": "1.1.0",
+ "karma-sinon-chai": "1.2.0",
"karma-webpack": "1.7.0",
- "lodash": "3.10.1",
- "mocha": "2.3.3",
- "ng-annotate": "1.0.2",
- "ng-annotate-loader": "0.0.10",
- "node-libs-browser": "0.5.3",
+ "lodash": "4.6.1",
+ "lolex": "1.4.0",
+ "mocha": "2.4.5",
+ "ng-annotate": "1.2.1",
+ "ng-annotate-loader": "0.1.0",
+ "node-libs-browser": "1.0.0",
"path-here": "1.1.0",
"publish-latest": "1.1.2",
"raw-loader": "0.5.1",
"semantic-release": "4.3.5",
- "sinon": "1.17.1",
+ "sinon": "1.17.3",
"sinon-chai": "2.8.0",
- "uglify-loader": "1.2.0",
- "validate-commit-msg": "1.0.0",
- "webpack": "1.12.2",
- "webpack-notifier": "1.2.1"
+ "validate-commit-msg": "2.3.1",
+ "webpack": "1.12.14",
+ "webpack-notifier": "1.3.0"
},
"jspm": {
"peerDependencies": {
"angular": "*"
}
},
- "czConfig": {
- "path": "node_modules/cz-conventional-changelog"
- },
"release": {
"verfiyRelease": {
"path": "cracks",
diff --git a/src/angular-fix/index.js b/src/angular-fix/index.js
index 295917ef..73bddb97 100644
--- a/src/angular-fix/index.js
+++ b/src/angular-fix/index.js
@@ -1,9 +1,9 @@
// some versions of angular don't export the angular module properly,
// so we get it from window in this case.
-let angular = require('angular');
+let angular = require('angular')
/* istanbul ignore next */
if (!angular.version) {
- angular = window.angular;
+ angular = window.angular
}
-export default angular;
+export default angular
diff --git a/src/directives/formly-custom-validation.js b/src/directives/formly-custom-validation.js
index e991cb6f..a5a3271c 100644
--- a/src/directives/formly-custom-validation.js
+++ b/src/directives/formly-custom-validation.js
@@ -1,5 +1,5 @@
-import angular from 'angular-fix';
-export default formlyCustomValidation;
+import angular from 'angular-fix'
+export default formlyCustomValidation
// @ngInject
function formlyCustomValidation(formlyUtil) {
@@ -7,76 +7,76 @@ function formlyCustomValidation(formlyUtil) {
restrict: 'A',
require: 'ngModel',
link: function formlyCustomValidationLink(scope, el, attrs, ctrl) {
- const opts = scope.options;
- opts.validation.messages = opts.validation.messages || {};
+ const opts = scope.options
+ opts.validation.messages = opts.validation.messages || {}
angular.forEach(opts.validation.messages, (message, key) => {
opts.validation.messages[key] = () => {
- return formlyUtil.formlyEval(scope, message, ctrl.$modelValue, ctrl.$viewValue);
- };
- });
+ return formlyUtil.formlyEval(scope, message, ctrl.$modelValue, ctrl.$viewValue)
+ }
+ })
- const useNewValidatorsApi = ctrl.hasOwnProperty('$validators') && !attrs.hasOwnProperty('useParsers');
- angular.forEach(opts.validators, angular.bind(null, addValidatorToPipeline, false));
- angular.forEach(opts.asyncValidators, angular.bind(null, addValidatorToPipeline, true));
+ const useNewValidatorsApi = ctrl.hasOwnProperty('$validators') && !attrs.hasOwnProperty('useParsers')
+ angular.forEach(opts.validators, angular.bind(null, addValidatorToPipeline, false))
+ angular.forEach(opts.asyncValidators, angular.bind(null, addValidatorToPipeline, true))
function addValidatorToPipeline(isAsync, validator, name) {
- setupMessage(validator, name);
- validator = angular.isObject(validator) ? validator.expression : validator;
+ setupMessage(validator, name)
+ validator = angular.isObject(validator) ? validator.expression : validator
if (useNewValidatorsApi) {
- setupWithValidators(validator, name, isAsync);
+ setupWithValidators(validator, name, isAsync)
} else {
- setupWithParsers(validator, name, isAsync);
+ setupWithParsers(validator, name, isAsync)
}
}
function setupMessage(validator, name) {
- const message = validator.message;
+ const message = validator.message
if (message) {
opts.validation.messages[name] = () => {
- return formlyUtil.formlyEval(scope, message, ctrl.$modelValue, ctrl.$viewValue);
- };
+ return formlyUtil.formlyEval(scope, message, ctrl.$modelValue, ctrl.$viewValue)
+ }
}
}
function setupWithValidators(validator, name, isAsync) {
- const validatorCollection = isAsync ? '$asyncValidators' : '$validators';
+ const validatorCollection = isAsync ? '$asyncValidators' : '$validators'
ctrl[validatorCollection][name] = function evalValidity(modelValue, viewValue) {
- return formlyUtil.formlyEval(scope, validator, modelValue, viewValue);
- };
+ return formlyUtil.formlyEval(scope, validator, modelValue, viewValue)
+ }
}
function setupWithParsers(validator, name, isAsync) {
- let inFlightValidator;
+ let inFlightValidator
ctrl.$parsers.unshift(function evalValidityOfParser(viewValue) {
- const isValid = formlyUtil.formlyEval(scope, validator, ctrl.$modelValue, viewValue);
+ const isValid = formlyUtil.formlyEval(scope, validator, ctrl.$modelValue, viewValue)
if (isAsync) {
- ctrl.$pending = ctrl.$pending || {};
- ctrl.$pending[name] = true;
- inFlightValidator = isValid;
+ ctrl.$pending = ctrl.$pending || {}
+ ctrl.$pending[name] = true
+ inFlightValidator = isValid
isValid.then(() => {
if (inFlightValidator === isValid) {
- ctrl.$setValidity(name, true);
+ ctrl.$setValidity(name, true)
}
}).catch(() => {
if (inFlightValidator === isValid) {
- ctrl.$setValidity(name, false);
+ ctrl.$setValidity(name, false)
}
}).finally(() => {
- const $pending = ctrl.$pending || {};
+ const $pending = ctrl.$pending || {}
if (Object.keys($pending).length === 1) {
- delete ctrl.$pending;
+ delete ctrl.$pending
} else {
- delete ctrl.$pending[name];
+ delete ctrl.$pending[name]
}
- });
+ })
} else {
- ctrl.$setValidity(name, isValid);
+ ctrl.$setValidity(name, isValid)
}
- return viewValue;
- });
+ return viewValue
+ })
}
- }
- };
+ },
+ }
}
diff --git a/src/directives/formly-custom-validation.test.js b/src/directives/formly-custom-validation.test.js
index cc9dd4e6..68fd72a1 100644
--- a/src/directives/formly-custom-validation.test.js
+++ b/src/directives/formly-custom-validation.test.js
@@ -1,128 +1,128 @@
/* eslint no-unused-vars:0, max-len:0 */
-import _ from 'lodash';
-import angular from 'angular-fix';
+import _ from 'lodash'
+import angular from 'angular-fix'
-import testUtils from '../test.utils.js';
+import testUtils from '../test.utils.js'
-const {shouldWarnWithLog} = testUtils;
+const {shouldWarnWithLog} = testUtils
describe(`formly-custom-validation`, function() {
- let $compile, $timeout, $q, scope, $log, formlyConfig;
- const formTemplate = `
`;
- beforeEach(window.module('formly'));
+ let $compile, $timeout, $q, scope, $log, formlyConfig
+ const formTemplate = ``
+ beforeEach(window.module('formly'))
beforeEach(inject((_$compile_, _$timeout_, _$q_, $rootScope, _$log_, _formlyConfig_) => {
- $compile = _$compile_;
- $timeout = _$timeout_;
- $q = _$q_;
- scope = $rootScope.$new();
- scope.options = {validation: {}, validators: {}, asyncValidators: {}};
- $log = _$log_;
- formlyConfig = _formlyConfig_;
- }));
+ $compile = _$compile_
+ $timeout = _$timeout_
+ $q = _$q_
+ scope = $rootScope.$new()
+ scope.options = {validation: {}, validators: {}, asyncValidators: {}}
+ $log = _$log_
+ formlyConfig = _formlyConfig_
+ }))
describe(`using parsers`, () => {
checkApi(formTemplate.replace(
`TEMPLATE`, ` `
- ), angular.version.minor >= 3);
- });
+ ), angular.version.minor >= 3)
+ })
describe(`using $validators`, () => {
checkApi(formTemplate.replace(
`TEMPLATE`, ` `
- ));
- });
+ ))
+ })
describe(`options.validation.messages`, () => {
it(`should convert all strings to functions`, () => {
scope.options.validation = {
messages: {
- isHello: `'"' + $viewValue + '" is not "hello"'`
- }
- };
+ isHello: `'"' + $viewValue + '" is not "hello"'`,
+ },
+ }
$compile(formTemplate.replace(
`TEMPLATE`, ` `
- ))(scope);
+ ))(scope)
- expect(typeof scope.options.validation.messages.isHello).to.eq('function');
- const field = scope.myForm.field;
- field.$setViewValue('sup');
- expect(scope.options.validation.messages.isHello()).to.eq('"sup" is not "hello"');
- });
- });
+ expect(typeof scope.options.validation.messages.isHello).to.eq('function')
+ const field = scope.myForm.field
+ field.$setViewValue('sup')
+ expect(scope.options.validation.messages.isHello()).to.eq('"sup" is not "hello"')
+ })
+ })
function checkApi(template, versionThreeOrBetterAndEmulating) {
- const value = `hello`;
+ const value = `hello`
describe(`validators`, () => {
- const validate = doValidation.bind(null, template, 'hello', false);
+ const validate = doValidation.bind(null, template, 'hello', false)
it(`should pass if returning a string that passes`, () => {
- validate(`$viewValue === "${value}"`, true);
- });
+ validate(`$viewValue === "${value}"`, true)
+ })
it(`should fail if returning a string that fails`, () => {
- validate(`$viewValue !== "${value}"`, false);
- });
+ validate(`$viewValue !== "${value}"`, false)
+ })
it(`should pass if it's a function that passes`, () => {
- validate(viewValue => viewValue === value, true);
- });
+ validate(viewValue => viewValue === value, true)
+ })
it(`should fail if it's a function that fails`, () => {
- validate(viewValue => viewValue !== value, false);
- });
- });
+ validate(viewValue => viewValue !== value, false)
+ })
+ })
describe(`asyncValidators`, () => {
- const validate = doValidation.bind(null, template, 'hello', true);
+ const validate = doValidation.bind(null, template, 'hello', true)
it(`should pass if it's a function that returns a promise that resolves`, () => {
- validate(() => $q.when(), true);
- });
+ validate(() => $q.when(), true)
+ })
it(`should fail if it's a function that returns a promise that rejects`, () => {
- validate(() => $q.reject(), false);
- });
+ validate(() => $q.reject(), false)
+ })
it(`should be pending until the promise is resolved`, () => {
- const deferred = $q.defer();
- const deferred2 = $q.defer();
- scope.options.asyncValidators.isHello = () => deferred.promise;
- scope.options.asyncValidators.isHey = () => deferred2.promise;
- $compile(template)(scope);
- const field = scope.myForm.field;
- scope.$digest();
- field.$setViewValue(value);
-
- expect(field.$pending).to.exist;
- expect(field.$pending.isHello).to.be.true;
- expect(field.$pending.isHey).to.be.true;
+ const deferred = $q.defer()
+ const deferred2 = $q.defer()
+ scope.options.asyncValidators.isHello = () => deferred.promise
+ scope.options.asyncValidators.isHey = () => deferred2.promise
+ $compile(template)(scope)
+ const field = scope.myForm.field
+ scope.$digest()
+ field.$setViewValue(value)
+
+ expect(field.$pending).to.exist
+ expect(field.$pending.isHello).to.be.true
+ expect(field.$pending.isHey).to.be.true
// because in angular 1.3 they do some interesting stuff with $pending, so can only test $pending in 1.2
if (!versionThreeOrBetterAndEmulating) {
- deferred.resolve();
- scope.$digest();
+ deferred.resolve()
+ scope.$digest()
- expect(field.$pending).to.exist;
- expect(field.$pending.isHey).to.be.true;
- expect(field.$pending.isHello).to.not.exist;
+ expect(field.$pending).to.exist
+ expect(field.$pending.isHey).to.be.true
+ expect(field.$pending.isHello).to.not.exist
- deferred2.reject();
- scope.$digest();
- expect(field.$pending).to.not.exist;
+ deferred2.reject()
+ scope.$digest()
+ expect(field.$pending).to.not.exist
}
- });
- });
+ })
+ })
}
function doValidation(template, value, isAsync, validator, pass) {
if (isAsync) {
- scope.options.asyncValidators.isHello = validator;
+ scope.options.asyncValidators.isHello = validator
} else {
- scope.options.validators.isHello = validator;
+ scope.options.validators.isHello = validator
}
- $compile(template)(scope);
- const field = scope.myForm.field;
- scope.$digest();
- field.$setViewValue(value);
- scope.$digest();
- expect(field.$valid).to.eq(pass);
+ $compile(template)(scope)
+ const field = scope.myForm.field
+ scope.$digest()
+ field.$setViewValue(value)
+ scope.$digest()
+ expect(field.$valid).to.eq(pass)
}
-});
+})
diff --git a/src/directives/formly-field.js b/src/directives/formly-field.js
index d5949ac0..d98e3e9a 100644
--- a/src/directives/formly-field.js
+++ b/src/directives/formly-field.js
@@ -1,7 +1,7 @@
-import angular from 'angular-fix';
-import apiCheckFactory from 'api-check';
+import angular from 'angular-fix'
+import apiCheckFactory from 'api-check'
-export default formlyField;
+export default formlyField
/**
* @ngdoc directive
@@ -11,7 +11,7 @@ export default formlyField;
// @ngInject
function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyConfig,
formlyApiCheck, formlyUtil, formlyUsability, formlyWarn) {
- const {arrayify} = formlyUtil;
+ const {arrayify} = formlyUtil
return {
restrict: 'AE',
@@ -26,60 +26,139 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
fields: '=?',
formState: '=?',
formOptions: '=?',
- form: '=?' // TODO require form in a breaking release
+ form: '=?', // TODO require form in a breaking release
},
controller: FormlyFieldController,
- link: fieldLink
- };
+ link: fieldLink,
+ }
// @ngInject
function FormlyFieldController($scope, $timeout, $parse, $controller, formlyValidationMessages) {
- /* eslint max-statements:[2, 31] */
+ /* eslint max-statements:[2, 37] */
if ($scope.options.fieldGroup) {
- setupFieldGroup();
- return;
+ setupFieldGroup()
+ return
}
- const fieldType = getFieldType($scope.options);
- simplifyLife($scope.options);
- mergeFieldOptionsWithTypeDefaults($scope.options, fieldType);
- extendOptionsWithDefaults($scope.options, $scope.index);
- checkApi($scope.options);
+ const fieldType = getFieldType($scope.options)
+ simplifyLife($scope.options)
+ mergeFieldOptionsWithTypeDefaults($scope.options, fieldType)
+ extendOptionsWithDefaults($scope.options, $scope.index)
+ checkApi($scope.options)
// set field id to link labels and fields
// initalization
- setFieldIdAndName();
- setDefaultValue();
- setInitialValue();
- runExpressions();
- addValidationMessages($scope.options);
- invokeControllers($scope, $scope.options, fieldType);
+ setFieldIdAndName()
+ setDefaultValue()
+ setInitialValue()
+ runExpressions()
+ watchExpressions()
+ addValidationMessages($scope.options)
+ invokeControllers($scope, $scope.options, fieldType)
// function definitions
function runExpressions() {
+ const deferred = $q.defer()
// must run on next tick to make sure that the current value is correct.
- return $timeout(function runExpressionsOnNextTick() {
- const field = $scope.options;
- const currentValue = valueGetterSetter();
+ $timeout(function runExpressionsOnNextTick() {
+ const promises = []
+ const field = $scope.options
+ const currentValue = valueGetterSetter()
angular.forEach(field.expressionProperties, function runExpression(expression, prop) {
- const setter = $parse(prop).assign;
- const promise = $q.when(formlyUtil.formlyEval($scope, expression, currentValue, currentValue));
- promise.then(function setFieldValue(value) {
- setter(field, value);
- });
- });
- }, 0, false);
+ const setter = $parse(prop).assign
+ const promise = $q.when(formlyUtil.formlyEval($scope, expression, currentValue, currentValue))
+ .then(function setFieldValue(value) {
+ setter(field, value)
+ })
+ promises.push(promise)
+ })
+ $q.all(promises).then(function() {
+ deferred.resolve()
+ })
+ }, 0, false)
+ return deferred.promise
+ }
+
+ function watchExpressions() {
+ if ($scope.formOptions.watchAllExpressions) {
+ const field = $scope.options
+ const currentValue = valueGetterSetter()
+ angular.forEach(field.expressionProperties, function watchExpression(expression, prop) {
+ const setter = $parse(prop).assign
+ $scope.$watch(function expressionPropertyWatcher() {
+ return formlyUtil.formlyEval($scope, expression, currentValue, currentValue)
+ }, function expressionPropertyListener(value) {
+ setter(field, value)
+ }, true)
+ })
+ }
}
function valueGetterSetter(newVal) {
if (!$scope.model || !$scope.options.key) {
- return undefined;
+ return undefined
}
if (angular.isDefined(newVal)) {
- $scope.model[$scope.options.key] = newVal;
+ parseSet($scope.options.key, $scope.model, newVal)
+ }
+ return parseGet($scope.options.key, $scope.model)
+ }
+
+ function shouldNotUseParseKey(key) {
+ return angular.isNumber(key) || !formlyUtil.containsSelector(key)
+ }
+
+ function keyContainsArrays(key) {
+ return /\[\d{1,}\]/.test(key)
+ }
+
+ function deepAssign(obj, prop, value) {
+ if (angular.isString(prop)) {
+ prop = prop.replace(/\[(\w+)\]/g, '.$1').split('.')
+ }
+
+ if (prop.length > 1) {
+ const e = prop.shift()
+ obj[e] = obj[e] || (isNaN(prop[0])) ? {} : []
+ deepAssign(obj[e], prop, value)
+ } else {
+ obj[prop[0]] = value
+ }
+ }
+
+ function parseSet(key, model, newVal) {
+ // If either of these are null/undefined then just return undefined
+ if ((!key && key !== 0) || !model) {
+ return
+ }
+ // If we are working with a number then $parse wont work, default back to the old way for now
+ if (shouldNotUseParseKey(key)) {
+ // TODO: Fix this so we can get several levels instead of just one with properties that are numeric
+ model[key] = newVal
+ } else if (formlyConfig.extras.parseKeyArrays && keyContainsArrays(key)) {
+ deepAssign($scope.model, key, newVal)
+ } else {
+ const setter = $parse($scope.options.key).assign
+ if (setter) {
+ setter($scope.model, newVal)
+ }
+ }
+ }
+
+ function parseGet(key, model) {
+ // If either of these are null/undefined then just return undefined
+ if ((!key && key !== 0) || !model) {
+ return undefined
+ }
+
+ // If we are working with a number then $parse wont work, default back to the old way for now
+ if (shouldNotUseParseKey(key)) {
+ // TODO: Fix this so we can get several levels instead of just one with properties that are numeric
+ return model[key]
+ } else {
+ return $parse(key)(model)
}
- return $scope.model[$scope.options.key];
}
function simplifyLife(options) {
@@ -89,121 +168,126 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
extras: {},
data: {},
templateOptions: {},
- validation: {}
- });
+ validation: {},
+ })
// create $scope.to so template authors can reference to instead of $scope.options.templateOptions
- $scope.to = $scope.options.templateOptions;
- $scope.formOptions = $scope.formOptions || {};
+ $scope.to = $scope.options.templateOptions
+ $scope.formOptions = $scope.formOptions || {}
}
function setFieldIdAndName() {
if (angular.isFunction(formlyConfig.extras.getFieldId)) {
- $scope.id = formlyConfig.extras.getFieldId($scope.options, $scope.model, $scope);
+ $scope.id = formlyConfig.extras.getFieldId($scope.options, $scope.model, $scope)
} else {
- const formName = ($scope.form && $scope.form.$name) || $scope.formId;
- $scope.id = formlyUtil.getFieldId(formName, $scope.options, $scope.index);
+ const formName = ($scope.form && $scope.form.$name) || $scope.formId
+ $scope.id = formlyUtil.getFieldId(formName, $scope.options, $scope.index)
}
- $scope.options.id = $scope.id;
- $scope.name = $scope.options.name || $scope.options.id;
- $scope.options.name = $scope.name;
+ $scope.options.id = $scope.id
+ $scope.name = $scope.options.name || $scope.options.id
+ $scope.options.name = $scope.name
}
function setDefaultValue() {
- if (angular.isDefined($scope.options.defaultValue) && !angular.isDefined($scope.model[$scope.options.key])) {
- const setter = $parse($scope.options.key).assign;
- setter($scope.model, $scope.options.defaultValue);
+ if (angular.isDefined($scope.options.defaultValue) &&
+ !angular.isDefined(parseGet($scope.options.key, $scope.model))) {
+ parseSet($scope.options.key, $scope.model, $scope.options.defaultValue)
}
}
function setInitialValue() {
- $scope.options.initialValue = $scope.model && $scope.model[$scope.options.key];
+ $scope.options.initialValue = $scope.model && parseGet($scope.options.key, $scope.model)
}
function mergeFieldOptionsWithTypeDefaults(options, type) {
if (type) {
- mergeOptions(options, type.defaultOptions);
+ mergeOptions(options, type.defaultOptions)
}
- const properOrder = arrayify(options.optionsTypes).reverse(); // so the right things are overridden
+ const properOrder = arrayify(options.optionsTypes).reverse() // so the right things are overridden
angular.forEach(properOrder, typeName => {
- mergeOptions(options, formlyConfig.getType(typeName, true, options).defaultOptions);
- });
+ mergeOptions(options, formlyConfig.getType(typeName, true, options).defaultOptions)
+ })
}
function mergeOptions(options, extraOptions) {
if (extraOptions) {
if (angular.isFunction(extraOptions)) {
- extraOptions = extraOptions(options, $scope);
+ extraOptions = extraOptions(options, $scope)
}
- formlyUtil.reverseDeepMerge(options, extraOptions);
+ formlyUtil.reverseDeepMerge(options, extraOptions)
}
}
function extendOptionsWithDefaults(options, index) {
- const key = options.key || index || 0;
+ const key = options.key || index || 0
angular.extend(options, {
// attach the key in case the formly-field directive is used directly
key,
value: options.value || valueGetterSetter,
runExpressions,
resetModel,
- updateInitialValue
- });
+ updateInitialValue,
+ })
}
function resetModel() {
- $scope.model[$scope.options.key] = $scope.options.initialValue;
+ parseSet($scope.options.key, $scope.model, $scope.options.initialValue)
if ($scope.options.formControl) {
if (angular.isArray($scope.options.formControl)) {
angular.forEach($scope.options.formControl, function(formControl) {
- resetFormControl(formControl, true);
- });
+ resetFormControl(formControl, true)
+ })
} else {
- resetFormControl($scope.options.formControl);
+ resetFormControl($scope.options.formControl)
}
}
+ if ($scope.form) {
+ $scope.form.$setUntouched && $scope.form.$setUntouched()
+ $scope.form.$setPristine()
+ }
}
function resetFormControl(formControl, isMultiNgModel) {
if (!isMultiNgModel) {
- formControl.$setViewValue($scope.model[$scope.options.key]);
+ formControl.$setViewValue(parseGet($scope.options.key, $scope.model))
}
- formControl.$render();
- formControl.$setUntouched && formControl.$setUntouched();
- formControl.$setPristine();
+ formControl.$render()
+ formControl.$setUntouched && formControl.$setUntouched()
+ formControl.$setPristine()
// To prevent breaking change requiring a digest to reset $viewModel
if (!$scope.$root.$$phase) {
- $scope.$digest();
+ $scope.$digest()
}
}
function updateInitialValue() {
- $scope.options.initialValue = $scope.model[$scope.options.key];
+ $scope.options.initialValue = parseGet($scope.options.key, $scope.model)
}
function addValidationMessages(options) {
- options.validation.messages = options.validation.messages || {};
+ options.validation.messages = options.validation.messages || {}
angular.forEach(formlyValidationMessages.messages, function createFunctionForMessage(expression, name) {
if (!options.validation.messages[name]) {
options.validation.messages[name] = function evaluateMessage(viewValue, modelValue, scope) {
- return formlyUtil.formlyEval(scope, expression, modelValue, viewValue);
- };
+ return formlyUtil.formlyEval(scope, expression, modelValue, viewValue)
+ }
}
- });
+ })
}
function invokeControllers(scope, options = {}, type = {}) {
angular.forEach([type.controller, options.controller], controller => {
if (controller) {
- $controller(controller, {$scope: scope});
+ $controller(controller, {$scope: scope})
}
- });
+ })
}
function setupFieldGroup() {
- $scope.options.options = $scope.options.options || {};
- $scope.options.options.formState = $scope.formState;
+ $scope.options.options = $scope.options.options || {}
+ $scope.options.options.formState = $scope.formState
+ $scope.to = $scope.options.templateOptions
}
}
@@ -211,23 +295,23 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
// link function
function fieldLink(scope, el, attrs, formlyFormCtrl) {
if (scope.options.fieldGroup) {
- setFieldGroupTemplate();
- return;
+ setFieldGroupTemplate()
+ return
}
// watch the field model (if exists) if there is no parent formly-form directive (that would watch it instead)
if (!formlyFormCtrl && scope.options.model) {
- scope.$watch('options.model', () => scope.options.runExpressions(), true);
+ scope.$watch('options.model', () => scope.options.runExpressions(), true)
}
- addAttributes();
- addClasses();
+ addAttributes()
+ addClasses()
- const type = getFieldType(scope.options);
- const args = arguments;
- const thusly = this;
- let fieldCount = 0;
- const fieldManipulators = getManipulators(scope.options, scope.formOptions);
+ const type = getFieldType(scope.options)
+ const args = arguments
+ const thusly = this
+ let fieldCount = 0
+ const fieldManipulators = getManipulators(scope.options, scope.formOptions)
getFieldTemplate(scope.options)
.then(runManipulators(fieldManipulators.preWrapper))
.then(transcludeInWrappers(scope.options, scope.formOptions))
@@ -241,24 +325,24 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
'There was a problem setting the template for this field ',
scope.options,
error
- );
- });
+ )
+ })
function setFieldGroupTemplate() {
- checkFieldGroupApi(scope.options);
- el.addClass('formly-field-group');
- let extraAttributes = '';
+ checkFieldGroupApi(scope.options)
+ el.addClass('formly-field-group')
+ let extraAttributes = ''
if (scope.options.elementAttributes) {
extraAttributes = Object.keys(scope.options.elementAttributes).map(key => {
- return `${key}="${scope.options.elementAttributes[key]}"`;
- }).join(' ');
+ return `${key}="${scope.options.elementAttributes[key]}"`
+ }).join(' ')
}
- let modelValue = 'model';
- scope.options.form = scope.form;
+ let modelValue = 'model'
+ scope.options.form = scope.form
if (scope.options.key) {
- modelValue = `model['${scope.options.key}']`;
+ modelValue = `model['${scope.options.key}']`
}
- setElementTemplate(`
+ getTemplate(`
- `);
+ `)
+ .then(transcludeInWrappers(scope.options, scope.formOptions))
+ .then(setElementTemplate)
}
function addAttributes() {
if (scope.options.elementAttributes) {
- el.attr(scope.options.elementAttributes);
+ el.attr(scope.options.elementAttributes)
}
}
function addClasses() {
if (scope.options.className) {
- el.addClass(scope.options.className);
+ el.addClass(scope.options.className)
}
if (scope.options.type) {
- el.addClass(`formly-field-${scope.options.type}`);
+ el.addClass(`formly-field-${scope.options.type}`)
}
}
function setElementTemplate(templateString) {
- el.html(asHtml(templateString));
- $compile(el.contents())(scope);
- return templateString;
+ el.html(asHtml(templateString))
+ $compile(el.contents())(scope)
+ return templateString
}
function watchFormControl(templateString) {
- let stopWatchingShowError = angular.noop;
+ let stopWatchingShowError = angular.noop
if (scope.options.noFormControl) {
- return;
+ return
}
- const templateEl = angular.element(`${templateString}
`);
- const ngModelNodes = templateEl[0].querySelectorAll('[ng-model],[data-ng-model]');
+ const templateEl = angular.element(`${templateString}
`)
+ const ngModelNodes = templateEl[0].querySelectorAll('[ng-model],[data-ng-model]')
if (ngModelNodes.length) {
angular.forEach(ngModelNodes, function(ngModelNode) {
- fieldCount++;
- watchFieldNameOrExistence(ngModelNode.getAttribute('name'));
- });
+ fieldCount++
+ watchFieldNameOrExistence(ngModelNode.getAttribute('name'))
+ })
}
function watchFieldNameOrExistence(name) {
- const nameExpressionRegex = /\{\{(.*?)}}/;
- const nameExpression = nameExpressionRegex.exec(name);
+ const nameExpressionRegex = /\{\{(.*?)}}/
+ const nameExpression = nameExpressionRegex.exec(name)
if (nameExpression) {
- name = $interpolate(name)(scope);
+ name = $interpolate(name)(scope)
}
- watchFieldExistence(name);
+ watchFieldExistence(name)
}
function watchFieldExistence(name) {
@@ -321,134 +407,138 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
if (formControl) {
if (fieldCount > 1) {
if (!scope.options.formControl) {
- scope.options.formControl = [];
+ scope.options.formControl = []
}
- scope.options.formControl.push(formControl);
+ scope.options.formControl.push(formControl)
} else {
- scope.options.formControl = formControl;
+ scope.options.formControl = formControl
}
- scope.fc = scope.options.formControl; // shortcut for template authors
- stopWatchingShowError();
- addShowMessagesWatcher();
- addParsers();
- addFormatters();
+ scope.fc = scope.options.formControl // shortcut for template authors
+ stopWatchingShowError()
+ addShowMessagesWatcher()
+ addParsers()
+ addFormatters()
}
- });
+ })
}
function addShowMessagesWatcher() {
stopWatchingShowError = scope.$watch(function watchShowValidationChange() {
- const customExpression = formlyConfig.extras.errorExistsAndShouldBeVisibleExpression;
- const {options, fc} = scope;
- if (!fc.$invalid) {
- return false;
+ const customExpression = formlyConfig.extras.errorExistsAndShouldBeVisibleExpression
+ const options = scope.options
+ const formControls = arrayify(scope.fc)
+ if (!formControls.some(fc => fc.$invalid)) {
+ return false
} else if (typeof options.validation.show === 'boolean') {
- return options.validation.show;
+ return options.validation.show
} else if (customExpression) {
- return formlyUtil.formlyEval(scope, customExpression, fc.$modelValue, fc.$viewValue);
+ return formControls.some(fc =>
+ formlyUtil.formlyEval(scope, customExpression, fc.$modelValue, fc.$viewValue))
} else {
- const noTouchedButDirty = (angular.isUndefined(fc.$touched) && fc.$dirty);
- return (scope.fc.$touched || noTouchedButDirty);
+ return formControls.some(fc => {
+ const noTouchedButDirty = (angular.isUndefined(fc.$touched) && fc.$dirty)
+ return (fc.$touched || noTouchedButDirty)
+ })
}
}, function onShowValidationChange(show) {
- scope.options.validation.errorExistsAndShouldBeVisible = show;
- scope.showError = show; // shortcut for template authors
- });
+ scope.options.validation.errorExistsAndShouldBeVisible = show
+ scope.showError = show // shortcut for template authors
+ })
}
function addParsers() {
- setParsersOrFormatters('parsers');
+ setParsersOrFormatters('parsers')
}
function addFormatters() {
- setParsersOrFormatters('formatters');
- const ctrl = scope.fc;
- const formWasPristine = scope.form.$pristine;
+ setParsersOrFormatters('formatters')
+ const ctrl = scope.fc
+ const formWasPristine = scope.form.$pristine
if (scope.options.formatters) {
- let value = ctrl.$modelValue;
+ let value = ctrl.$modelValue
ctrl.$formatters.forEach((formatter) => {
- value = formatter(value);
- });
+ value = formatter(value)
+ })
- ctrl.$setViewValue(value);
- ctrl.$render();
- ctrl.$setPristine();
+ ctrl.$setViewValue(value)
+ ctrl.$render()
+ ctrl.$setPristine()
if (formWasPristine) {
- scope.form.$setPristine();
+ scope.form.$setPristine()
}
}
}
function setParsersOrFormatters(which) {
- let originalThingProp = 'originalParser';
+ let originalThingProp = 'originalParser'
if (which === 'formatters') {
- originalThingProp = 'originalFormatter';
+ originalThingProp = 'originalFormatter'
}
// init with type's parsers
- let things = getThingsFromType(type);
+ let things = getThingsFromType(type)
// get optionsTypes things
- things = formlyUtil.extendArray(things, getThingsFromOptionsTypes(scope.options.optionsTypes));
+ things = formlyUtil.extendArray(things, getThingsFromOptionsTypes(scope.options.optionsTypes))
// get field's things
- things = formlyUtil.extendArray(things, scope.options[which]);
+ things = formlyUtil.extendArray(things, scope.options[which])
// convert things into formlyExpression things
angular.forEach(things, (thing, index) => {
- things[index] = getFormlyExpressionThing(thing);
- });
+ things[index] = getFormlyExpressionThing(thing)
+ })
- let ngModelCtrls = scope.fc;
+ let ngModelCtrls = scope.fc
if (!angular.isArray(ngModelCtrls)) {
- ngModelCtrls = [ngModelCtrls];
+ ngModelCtrls = [ngModelCtrls]
}
angular.forEach(ngModelCtrls, ngModelCtrl => {
- ngModelCtrl['$' + which] = ngModelCtrl['$' + which].concat(...things);
- });
+ ngModelCtrl['$' + which] = ngModelCtrl['$' + which].concat(...things)
+ })
function getThingsFromType(theType) {
if (!theType) {
- return [];
+ return []
}
if (angular.isString(theType)) {
- theType = formlyConfig.getType(theType, true, scope.options);
+ theType = formlyConfig.getType(theType, true, scope.options)
}
- let typeThings = [];
+ let typeThings = []
// get things from parent
if (theType.extends) {
- typeThings = formlyUtil.extendArray(typeThings, getThingsFromType(theType.extends));
+ typeThings = formlyUtil.extendArray(typeThings, getThingsFromType(theType.extends))
}
// get own type's things
- typeThings = formlyUtil.extendArray(typeThings, getDefaultOptionsProperty(theType, which, []));
+ typeThings = formlyUtil.extendArray(typeThings, getDefaultOptionsProperty(theType, which, []))
// get things from optionsTypes
typeThings = formlyUtil.extendArray(
typeThings,
getThingsFromOptionsTypes(getDefaultOptionsOptionsTypes(theType))
- );
+ )
- return typeThings;
+ return typeThings
}
function getThingsFromOptionsTypes(optionsTypes = []) {
- let optionsTypesThings = [];
+ let optionsTypesThings = []
angular.forEach(angular.copy(arrayify(optionsTypes)).reverse(), optionsTypeName => {
- optionsTypesThings = formlyUtil.extendArray(optionsTypesThings, getThingsFromType(optionsTypeName));
- });
- return optionsTypesThings;
+ optionsTypesThings = formlyUtil.extendArray(optionsTypesThings, getThingsFromType(optionsTypeName))
+ })
+ return optionsTypesThings
}
function getFormlyExpressionThing(thing) {
- formlyExpressionParserOrFormatterFunction[originalThingProp] = thing;
- return formlyExpressionParserOrFormatterFunction;
+ formlyExpressionParserOrFormatterFunction[originalThingProp] = thing
+ return formlyExpressionParserOrFormatterFunction
function formlyExpressionParserOrFormatterFunction($viewValue) {
- const $modelValue = scope.options.value();
- return formlyUtil.formlyEval(scope, thing, $modelValue, $viewValue);
+ const $modelValue = scope.options.value()
+ return formlyUtil.formlyEval(scope, thing, $modelValue, $viewValue)
}
}
@@ -457,46 +547,46 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
function callLinkFunctions() {
if (type && type.link) {
- type.link.apply(thusly, args);
+ type.link.apply(thusly, args)
}
if (scope.options.link) {
- scope.options.link.apply(thusly, args);
+ scope.options.link.apply(thusly, args)
}
}
function runManipulators(manipulators) {
return function runManipulatorsOnTemplate(templateToManipulate) {
- let chain = $q.when(templateToManipulate);
+ let chain = $q.when(templateToManipulate)
angular.forEach(manipulators, manipulator => {
chain = chain.then(template => {
return $q.when(manipulator(template, scope.options, scope)).then(newTemplate => {
- return angular.isString(newTemplate) ? newTemplate : asHtml(newTemplate);
- });
- });
- });
- return chain;
- };
+ return angular.isString(newTemplate) ? newTemplate : asHtml(newTemplate)
+ })
+ })
+ })
+ return chain
+ }
}
}
// sort-of stateless util functions
function asHtml(el) {
- const wrapper = angular.element(' ');
- return wrapper.append(el).html();
+ const wrapper = angular.element(' ')
+ return wrapper.append(el).html()
}
function getFieldType(options) {
- return options.type && formlyConfig.getType(options.type);
+ return options.type && formlyConfig.getType(options.type)
}
function getManipulators(options, formOptions) {
- let preWrapper = [];
- let postWrapper = [];
- addManipulators(options.templateManipulators);
- addManipulators(formOptions.templateManipulators);
- addManipulators(formlyConfig.templateManipulators);
- return {preWrapper, postWrapper};
+ let preWrapper = []
+ let postWrapper = []
+ addManipulators(options.templateManipulators)
+ addManipulators(formOptions.templateManipulators)
+ addManipulators(formlyConfig.templateManipulators)
+ return {preWrapper, postWrapper}
function addManipulators(manipulators) {
/* eslint-disable */ // it doesn't understand this :-(
@@ -510,38 +600,38 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
function getFieldTemplate(options) {
function fromOptionsOrType(key, fieldType) {
if (angular.isDefined(options[key])) {
- return options[key];
+ return options[key]
} else if (fieldType && angular.isDefined(fieldType[key])) {
- return fieldType[key];
+ return fieldType[key]
}
}
- const type = formlyConfig.getType(options.type, true, options);
- const template = fromOptionsOrType('template', type);
- const templateUrl = fromOptionsOrType('templateUrl', type);
+ const type = formlyConfig.getType(options.type, true, options)
+ const template = fromOptionsOrType('template', type)
+ const templateUrl = fromOptionsOrType('templateUrl', type)
if (angular.isUndefined(template) && !templateUrl) {
throw formlyUsability.getFieldError(
'type-type-has-no-template',
`Type '${options.type}' has no template. On element:`, options
- );
+ )
}
- return getTemplate(templateUrl || template, angular.isUndefined(template), options);
+ return getTemplate(templateUrl || template, angular.isUndefined(template), options)
}
function getTemplate(template, isUrl, options) {
- let templatePromise;
+ let templatePromise
if (angular.isFunction(template)) {
- templatePromise = $q.when(template(options));
+ templatePromise = $q.when(template(options))
} else {
- templatePromise = $q.when(template);
+ templatePromise = $q.when(template)
}
if (!isUrl) {
- return templatePromise;
+ return templatePromise
} else {
- const httpOptions = {cache: $templateCache};
+ const httpOptions = {cache: $templateCache}
return templatePromise
.then((url) => $http.get(url, httpOptions))
.then((response) => response.data)
@@ -550,142 +640,142 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
'problem-loading-template-for-templateurl',
'Problem loading template for ' + template,
error
- );
- });
+ )
+ })
}
}
function transcludeInWrappers(options, formOptions) {
- const wrapper = getWrapperOption(options, formOptions);
+ const wrapper = getWrapperOption(options, formOptions)
return function transcludeTemplate(template) {
if (!wrapper.length) {
- return $q.when(template);
+ return $q.when(template)
}
wrapper.forEach((aWrapper) => {
- formlyUsability.checkWrapper(aWrapper, options);
- runApiCheck(aWrapper, options);
- });
- const promises = wrapper.map(w => getTemplate(w.template || w.templateUrl, !w.template));
+ formlyUsability.checkWrapper(aWrapper, options)
+ runApiCheck(aWrapper, options)
+ })
+ const promises = wrapper.map(w => getTemplate(w.template || w.templateUrl, !w.template))
return $q.all(promises).then(wrappersTemplates => {
wrappersTemplates.forEach((wrapperTemplate, index) => {
- formlyUsability.checkWrapperTemplate(wrapperTemplate, wrapper[index]);
- });
- wrappersTemplates.reverse(); // wrapper 0 is wrapped in wrapper 1 and so on...
- let totalWrapper = wrappersTemplates.shift();
+ formlyUsability.checkWrapperTemplate(wrapperTemplate, wrapper[index])
+ })
+ wrappersTemplates.reverse() // wrapper 0 is wrapped in wrapper 1 and so on...
+ let totalWrapper = wrappersTemplates.shift()
wrappersTemplates.forEach(wrapperTemplate => {
- totalWrapper = doTransclusion(totalWrapper, wrapperTemplate);
- });
- return doTransclusion(totalWrapper, template);
- });
- };
+ totalWrapper = doTransclusion(totalWrapper, wrapperTemplate)
+ })
+ return doTransclusion(totalWrapper, template)
+ })
+ }
}
function doTransclusion(wrapper, template) {
- const superWrapper = angular.element(' '); // this allows people not have to have a single root in wrappers
- superWrapper.append(wrapper);
- let transcludeEl = superWrapper.find('formly-transclude');
+ const superWrapper = angular.element(' ') // this allows people not have to have a single root in wrappers
+ superWrapper.append(wrapper)
+ let transcludeEl = superWrapper.find('formly-transclude')
if (!transcludeEl.length) {
// try it using our custom find function
- transcludeEl = formlyUtil.findByNodeName(superWrapper, 'formly-transclude');
+ transcludeEl = formlyUtil.findByNodeName(superWrapper, 'formly-transclude')
}
- transcludeEl.replaceWith(template);
- return superWrapper.html();
+ transcludeEl.replaceWith(template)
+ return superWrapper.html()
}
function getWrapperOption(options, formOptions) {
/* eslint complexity:[2, 6] */
- let wrapper = options.wrapper;
+ let wrapper = options.wrapper
// explicit null means no wrapper
if (wrapper === null) {
- return [];
+ return []
}
// nothing specified means use the default wrapper for the type
if (!wrapper) {
// get all wrappers that specify they apply to this type
- wrapper = arrayify(formlyConfig.getWrapperByType(options.type));
+ wrapper = arrayify(formlyConfig.getWrapperByType(options.type))
} else {
- wrapper = arrayify(wrapper).map(formlyConfig.getWrapper);
+ wrapper = arrayify(wrapper).map(formlyConfig.getWrapper)
}
// get all wrappers for that the type specified that it uses.
- const type = formlyConfig.getType(options.type, true, options);
+ const type = formlyConfig.getType(options.type, true, options)
if (type && type.wrapper) {
- const typeWrappers = arrayify(type.wrapper).map(formlyConfig.getWrapper);
- wrapper = wrapper.concat(typeWrappers);
+ const typeWrappers = arrayify(type.wrapper).map(formlyConfig.getWrapper)
+ wrapper = wrapper.concat(typeWrappers)
}
// add form wrappers
if (formOptions.wrapper) {
- const formWrappers = arrayify(formOptions.wrapper).map(formlyConfig.getWrapper);
- wrapper = wrapper.concat(formWrappers);
+ const formWrappers = arrayify(formOptions.wrapper).map(formlyConfig.getWrapper)
+ wrapper = wrapper.concat(formWrappers)
}
// add the default wrapper last
- const defaultWrapper = formlyConfig.getWrapper();
+ const defaultWrapper = formlyConfig.getWrapper()
if (defaultWrapper) {
- wrapper.push(defaultWrapper);
+ wrapper.push(defaultWrapper)
}
- return wrapper;
+ return wrapper
}
function checkApi(options) {
formlyApiCheck.throw(formlyApiCheck.formlyFieldOptions, options, {
prefix: 'formly-field directive',
- url: 'formly-field-directive-validation-failed'
- });
+ url: 'formly-field-directive-validation-failed',
+ })
// validate with the type
- const type = options.type && formlyConfig.getType(options.type);
+ const type = options.type && formlyConfig.getType(options.type)
if (type) {
- runApiCheck(type, options, true);
+ runApiCheck(type, options, true)
}
if (options.expressionProperties && options.expressionProperties.hide) {
formlyWarn(
'dont-use-expressionproperties.hide-use-hideexpression-instead',
'You have specified `hide` in `expressionProperties`. Use `hideExpression` instead',
options
- );
+ )
}
}
function checkFieldGroupApi(options) {
formlyApiCheck.throw(formlyApiCheck.fieldGroup, options, {
prefix: 'formly-field directive',
- url: 'formly-field-directive-validation-failed'
- });
+ url: 'formly-field-directive-validation-failed',
+ })
}
function runApiCheck({apiCheck, apiCheckInstance, apiCheckFunction, apiCheckOptions}, options, forType) {
- runApiCheckForType(apiCheck, apiCheckInstance, apiCheckFunction, apiCheckOptions, options);
+ runApiCheckForType(apiCheck, apiCheckInstance, apiCheckFunction, apiCheckOptions, options)
if (forType && options.type) {
angular.forEach(formlyConfig.getTypeHeritage(options.type), function(type) {
- runApiCheckForType(type.apiCheck, type.apiCheckInstance, type.apiCheckFunction, type.apiCheckOptions, options);
- });
+ runApiCheckForType(type.apiCheck, type.apiCheckInstance, type.apiCheckFunction, type.apiCheckOptions, options)
+ })
}
}
function runApiCheckForType(apiCheck, apiCheckInstance, apiCheckFunction, apiCheckOptions, options) {
/* eslint complexity:[2, 9] */
if (!apiCheck) {
- return;
+ return
}
- const instance = apiCheckInstance || formlyConfig.extras.apiCheckInstance || formlyApiCheck;
+ const instance = apiCheckInstance || formlyConfig.extras.apiCheckInstance || formlyApiCheck
if (instance.config.disabled || apiCheckFactory.globalConfig.disabled) {
- return;
+ return
}
- const fn = apiCheckFunction || 'warn';
+ const fn = apiCheckFunction || 'warn'
// this is the new API
- const checkerObjects = apiCheck(instance);
+ const checkerObjects = apiCheck(instance)
angular.forEach(checkerObjects, (shape, name) => {
- const checker = instance.shape(shape);
+ const checker = instance.shape(shape)
const checkOptions = angular.extend({
prefix: `formly-field type ${options.type} for property ${name}`,
- url: formlyApiCheck.config.output.docsBaseUrl + 'formly-field-type-apicheck-failed'
- }, apiCheckOptions);
- instance[fn](checker, options[name], checkOptions);
- });
+ url: formlyApiCheck.config.output.docsBaseUrl + 'formly-field-type-apicheck-failed',
+ }, apiCheckOptions)
+ instance[fn](checker, options[name], checkOptions)
+ })
}
@@ -694,9 +784,9 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
// Stateless util functions
function getDefaultOptionsOptionsTypes(type) {
- return getDefaultOptionsProperty(type, 'optionsTypes', []);
+ return getDefaultOptionsProperty(type, 'optionsTypes', [])
}
function getDefaultOptionsProperty(type, prop, defaultValue) {
- return type.defaultOptions && type.defaultOptions[prop] || defaultValue;
+ return type.defaultOptions && type.defaultOptions[prop] || defaultValue
}
diff --git a/src/directives/formly-field.test.js b/src/directives/formly-field.test.js
index a4afd297..8daad3d1 100644
--- a/src/directives/formly-field.test.js
+++ b/src/directives/formly-field.test.js
@@ -1,26 +1,26 @@
/* eslint no-shadow:0 */
/* eslint max-statements:[2, 50] */
/* eslint max-len:0 */
-import angular from 'angular-fix';
-import apiCheck from 'api-check';
-import testUtils from '../test.utils.js';
-import _ from 'lodash';
+import angular from 'angular-fix'
+import apiCheck from 'api-check'
+import testUtils from '../test.utils.js'
+import _ from 'lodash'
-const {getNewField, input, basicForm, multiNgModelField, shouldWarn, shouldNotWarn} = testUtils;
+const {getNewField, input, basicForm, multiNgModelField, shouldWarn, shouldNotWarn} = testUtils
describe('formly-field', function() {
/* jshint maxstatements:100 */
/* jshint maxlen:300 */
- let $compile, scope, el, node, formlyConfig, $q, isolateScope, field, $timeout;
+ let $compile, scope, el, node, formlyConfig, $q, isolateScope, field, $timeout
- beforeEach(window.module('formly'));
+ beforeEach(window.module('formly'))
beforeEach(inject((_$compile_, $rootScope, _formlyConfig_, _$q_, _$timeout_) => {
- $compile = _$compile_;
- scope = $rootScope.$new();
- formlyConfig = _formlyConfig_;
- $q = _$q_;
- $timeout = _$timeout_;
- }));
+ $compile = _$compile_
+ scope = $rootScope.$new()
+ formlyConfig = _formlyConfig_
+ $q = _$q_
+ $timeout = _$timeout_
+ }))
describe('with template wrapper', function() {
beforeEach(() => {
@@ -32,7 +32,7 @@ describe('formly-field', function() {
{{options.label}}
- `
+ `,
},
{
types: 'other',
@@ -43,14 +43,14 @@ describe('formly-field', function() {
This is great for ng-messages
- `
- }
- ]);
+ `,
+ },
+ ])
formlyConfig.setType({
- name: 'text', template: ` `
- });
- scope.model = {};
- });
+ name: 'text', template: ` `,
+ })
+ scope.model = {}
+ })
it('should take the entire wrapper, not just the contents of the wrapper', function() {
scope.fields = [
@@ -58,13 +58,13 @@ describe('formly-field', function() {
type: 'text',
key: 'text',
templateOptions: {
- label: 'Text input'
- }
- }
- ];
- const el = compileAndDigest();
- expect(el[0].querySelector('.my-template-wrapper')).to.exist;
- });
+ label: 'Text input',
+ },
+ },
+ ]
+ const el = compileAndDigest()
+ expect(el[0].querySelector('.my-template-wrapper')).to.exist
+ })
it('should wrap arrays of wrappers', () => {
scope.fields = [
@@ -73,15 +73,15 @@ describe('formly-field', function() {
key: 'text',
wrapper: ['text', 'other'],
templateOptions: {
- label: 'Text input'
- }
- }
- ];
- const el = compileAndDigest();
- const outerEl = el[0].querySelector('.my-other-template-wrapper');
- expect(outerEl).to.exist;
- expect(outerEl.querySelector('.my-template-wrapper')).to.exist;
- });
+ label: 'Text input',
+ },
+ },
+ ]
+ const el = compileAndDigest()
+ const outerEl = el[0].querySelector('.my-other-template-wrapper')
+ expect(outerEl).to.exist
+ expect(outerEl.querySelector('.my-template-wrapper')).to.exist
+ })
it(`should allow for specifying null for the wrappers of a field`, () => {
scope.fields = [
@@ -90,524 +90,648 @@ describe('formly-field', function() {
key: 'text',
wrapper: null,
templateOptions: {
- label: 'Text input'
- }
- }
- ];
- const el = compileAndDigest();
- expect(el[0].querySelector('.my-template-wrapper')).to.not.exist;
- });
+ label: 'Text input',
+ },
+ },
+ ]
+ const el = compileAndDigest()
+ expect(el[0].querySelector('.my-template-wrapper')).to.not.exist
+ })
- });
+ })
describe('api check', () => {
beforeEach(() => {
/* eslint no-console:0 */
- const originalWarn = console.warn;
- console.warn = () => {};
+ const originalWarn = console.warn
+ console.warn = () => {}
formlyConfig.setType({
- name: 'text', template: ` `
- });
- scope.model = {};
- console.warn = originalWarn;
- });
+ name: 'text', template: ` `,
+ })
+ scope.model = {}
+ console.warn = originalWarn
+ })
it('should throw an error when a field has extra properties', () => {
scope.fields = [
{
type: 'text',
- extraProp: 'whatever'
- }
- ];
+ extraProp: 'whatever',
+ },
+ ]
- expect(() => compileAndDigest()).to.throw(/extra.*properties.*extraProp/);
- });
- });
+ expect(() => compileAndDigest()).to.throw(/extra.*properties.*extraProp/)
+ })
+ })
describe('default type options', () => {
beforeEach(() => {
- scope.model = {};
+ scope.model = {}
formlyConfig.setType({
name: 'ipAddress', template: ' ',
defaultOptions: {
data: {
- usingDefaultOptions: true
+ usingDefaultOptions: true,
},
validators: {
ipAddress: function(viewValue, modelValue) {
- const value = modelValue || viewValue;
- return /(\d{1,3}\.){3}\d{1,3}/.test(value);
- }
- }
- }
- });
+ const value = modelValue || viewValue
+ return /(\d{1,3}\.){3}\d{1,3}/.test(value)
+ },
+ },
+ },
+ })
formlyConfig.setType({
name: 'text', template: ' ',
defaultOptions: {
data: {
- hasPropertiesFromTextType: true
- }
- }
- });
+ hasPropertiesFromTextType: true,
+ },
+ },
+ })
formlyConfig.setType({
name: 'phone',
defaultOptions: {
ngModelAttrs: {
'/^1[2-9]\\d{2}[2-9]\\d{6}$/': {
- value: 'ng-pattern'
- }
- }
- }
- });
+ value: 'ng-pattern',
+ },
+ },
+ },
+ })
formlyConfig.setType({
name: 'required',
defaultOptions: {
ngModelAttrs: {
'/overwriting stuff is fun for tests/': {
- value: 'ng-pattern'
+ value: 'ng-pattern',
},
required: {
bound: 'ng-required',
- attribute: 'required'
+ attribute: 'required',
},
myChange: {
- statement: 'ng-change'
- }
+ statement: 'ng-change',
+ },
},
templateOptions: {
- required: true
- }
- }
- });
- });
+ required: true,
+ },
+ },
+ })
+ })
it('should default to the ipAddress type options', () => {
- const field = {type: 'ipAddress'};
- scope.fields = [field];
- compileAndDigest();
- expect(field.data.usingDefaultOptions).to.be.true;
- expect(field.validators.ipAddress).to.be.a('function');
- });
+ const field = {type: 'ipAddress'}
+ scope.fields = [field]
+ compileAndDigest()
+ expect(field.data.usingDefaultOptions).to.be.true
+ expect(field.validators.ipAddress).to.be.a('function')
+ })
it('should be possible to specify defaultOptions-only types (non-template types)', () => {
const field = {
- type: 'text', optionsTypes: ['phone', 'required'], templateOptions: {myChange: 'model.otherThing = true'}
- };
- scope.fields = [field];
- const el = compileAndDigest();
- const input = el.find('input');
- expect(field.data.hasPropertiesFromTextType).to.be.true;
- expect(input.attr('ng-pattern')).to.equal('/overwriting stuff is fun for tests/');
- expect(input.attr('ng-change')).to.contain('myChange');
- });
- });
+ type: 'text', optionsTypes: ['phone', 'required'], templateOptions: {myChange: 'model.otherThing = true'},
+ }
+ scope.fields = [field]
+ const el = compileAndDigest()
+ const input = el.find('input')
+ expect(field.data.hasPropertiesFromTextType).to.be.true
+ expect(input.attr('ng-pattern')).to.equal('/overwriting stuff is fun for tests/')
+ expect(input.attr('ng-change')).to.contain('myChange')
+ })
+ })
describe('templateManipulators', () => {
- testTemplateManipulators(true);
- testTemplateManipulators(false);
+ testTemplateManipulators(true)
+ testTemplateManipulators(false)
function testTemplateManipulators(isPre) {
describe(isPre ? 'preWrapper' : 'postWrapper', () => {
- let manipulators;
- const textTemplate = ' ';
+ let manipulators
+ const textTemplate = ' '
beforeEach(() => {
- manipulators = formlyConfig.templateManipulators[isPre ? 'preWrapper' : 'postWrapper'];
+ manipulators = formlyConfig.templateManipulators[isPre ? 'preWrapper' : 'postWrapper']
formlyConfig.setWrapper([
{
types: 'text',
- template: '
'
- }
- ]);
+ template: '
',
+ },
+ ])
formlyConfig.setType({
- name: 'text', template: textTemplate
- });
- scope.model = {};
+ name: 'text', template: textTemplate,
+ })
+ scope.model = {}
scope.fields = [
- {type: 'text'}
- ];
- });
+ {type: 'text'},
+ ]
+ })
- const when = isPre ? 'before' : 'after';
+ const when = isPre ? 'before' : 'after'
it(`should call the manipulators when compiling a field ${when} the element is wrapped in wrappers`, () => {
- let manipulatedTemplate;
+ let manipulatedTemplate
manipulators.push((templateToManipulate, fieldOptions, scope) => {
if (isPre) {
- expect(templateToManipulate).to.contain('text-template');
+ expect(templateToManipulate).to.contain('text-template')
}
- expect(fieldOptions).to.equal(scope.fields[0]);
- expect(scope.options).to.equal(fieldOptions);
+ expect(fieldOptions).to.equal(scope.fields[0])
+ expect(scope.options).to.equal(fieldOptions)
if (isPre) {
- expect(templateToManipulate).to.not.contain('my-template-wrapper');
+ expect(templateToManipulate).to.not.contain('my-template-wrapper')
} else {
- expect(templateToManipulate).to.contain('my-template-wrapper');
+ expect(templateToManipulate).to.contain('my-template-wrapper')
}
- manipulatedTemplate = angular.element(templateToManipulate).addClass('manipulated');
- return manipulatedTemplate;
- });
+ manipulatedTemplate = angular.element(templateToManipulate).addClass('manipulated')
+ return manipulatedTemplate
+ })
manipulators.push((templateToManipulate, fieldOptions, scope) => {
if (isPre) {
- expect(asHtml(manipulatedTemplate)).to.equal(templateToManipulate);
+ expect(asHtml(manipulatedTemplate)).to.equal(templateToManipulate)
}
- expect(fieldOptions).to.equal(scope.fields[0]);
- expect(scope.options).to.equal(fieldOptions);
+ expect(fieldOptions).to.equal(scope.fields[0])
+ expect(scope.options).to.equal(fieldOptions)
if (isPre) {
- expect(templateToManipulate).to.not.contain('my-template-wrapper');
+ expect(templateToManipulate).to.not.contain('my-template-wrapper')
} else {
- expect(templateToManipulate).to.contain('my-template-wrapper');
+ expect(templateToManipulate).to.contain('my-template-wrapper')
}
- expect(templateToManipulate).to.contain('manipulated');
- return angular.element(templateToManipulate).addClass('manipulated-twice');
- });
- compileAndDigest();
- scope.$digest();
- expect(el[0].querySelector('.manipulated')).to.exist;
- expect(el[0].querySelector('.manipulated-twice')).to.exist;
+ expect(templateToManipulate).to.contain('manipulated')
+ return angular.element(templateToManipulate).addClass('manipulated-twice')
+ })
+ compileAndDigest()
+ scope.$digest()
+ expect(el[0].querySelector('.manipulated')).to.exist
+ expect(el[0].querySelector('.manipulated-twice')).to.exist
function asHtml(el) {
- return angular.element(' ').append(el).html();
+ return angular.element(' ').append(el).html()
}
- });
- });
+ })
+ })
}
- });
+ })
describe('type controllers and link functions', () => {
- let controllerFn, linkFn;
+ let controllerFn, linkFn
beforeEach(() => {
controllerFn = function($scope) {
- $scope.setInTypeController = true;
- };
+ $scope.setInTypeController = true
+ }
linkFn = function(scope, el, attrs) {
- scope.setInTypeLink = true;
- scope.el = el;
- scope.attrs = attrs;
- };
+ scope.setInTypeLink = true
+ scope.el = el
+ scope.attrs = attrs
+ }
formlyConfig.setType({
name: 'text',
template: ` `,
controller: ['$scope', controllerFn],
- link: linkFn
- });
- scope.model = {};
- });
+ link: linkFn,
+ })
+ scope.model = {}
+ })
it('should run the controller function of a type', () => {
scope.fields = [
- {type: 'text'}
- ];
- const el = compileAndDigest();
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.isolateScope().setInTypeController).to.be.true;
- });
+ {type: 'text'},
+ ]
+ const el = compileAndDigest()
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.isolateScope().setInTypeController).to.be.true
+ })
it('should run the link function of a type', () => {
scope.fields = [
- {type: 'text'}
- ];
- const el = compileAndDigest();
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- const fieldScope = fieldEl.isolateScope();
- expect(fieldScope.setInTypeLink).to.be.true;
- expect(fieldScope.el[0]).to.equal(fieldEl[0]);
- });
+ {type: 'text'},
+ ]
+ const el = compileAndDigest()
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ const fieldScope = fieldEl.isolateScope()
+ expect(fieldScope.setInTypeLink).to.be.true
+ expect(fieldScope.el[0]).to.equal(fieldEl[0])
+ })
it('should run the controller of the specific field', () => {
scope.fields = [
- {template: 'sweet mercy', controller: ['$scope', controllerFn]}
- ];
+ {template: 'sweet mercy', controller: ['$scope', controllerFn]},
+ ]
- const el = compileAndDigest();
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.isolateScope().setInTypeController).to.be.true;
- });
+ const el = compileAndDigest()
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.isolateScope().setInTypeController).to.be.true
+ })
it('should run the link function of a type', () => {
scope.fields = [
- {template: 'sweet mercy', link: linkFn}
- ];
- const el = compileAndDigest();
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- const fieldScope = fieldEl.isolateScope();
- expect(fieldScope.setInTypeLink).to.be.true;
- expect(fieldScope.el[0]).to.equal(fieldEl[0]);
- });
- });
+ {template: 'sweet mercy', link: linkFn},
+ ]
+ const el = compileAndDigest()
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ const fieldScope = fieldEl.isolateScope()
+ expect(fieldScope.setInTypeLink).to.be.true
+ expect(fieldScope.el[0]).to.equal(fieldEl[0])
+ })
+ })
describe(`template and templateUrl properties`, () => {
- let $templateCache;
- const expectedTemplateText = 'sweet mercy';
+ let $templateCache
+ const expectedTemplateText = 'sweet mercy'
beforeEach(inject((_$templateCache_) => {
- $templateCache = _$templateCache_;
- $templateCache.put('templateUrlTest.html', expectedTemplateText);
- }));
+ $templateCache = _$templateCache_
+ $templateCache.put('templateUrlTest.html', expectedTemplateText)
+ }))
it('should allow template property to be a function', () => {
scope.fields = [
{
template: function(options) {
- expect(options).to.eq(scope.fields[0]);
- return expectedTemplateText;
- }
- }
- ];
+ expect(options).to.eq(scope.fields[0])
+ return expectedTemplateText
+ },
+ },
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal(expectedTemplateText);
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal(expectedTemplateText)
+ })
it(`should allow template property to be a function that returns a promise`, () => {
scope.fields = [
{
template: function(options) {
- expect(options).to.eq(scope.fields[0]);
- return $q.when(expectedTemplateText);
- }
- }
- ];
+ expect(options).to.eq(scope.fields[0])
+ return $q.when(expectedTemplateText)
+ },
+ },
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal(expectedTemplateText);
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal(expectedTemplateText)
+ })
it('should allow template property to be a string', () => {
scope.fields = [
- {template: expectedTemplateText}
- ];
+ {template: expectedTemplateText},
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal(expectedTemplateText);
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal(expectedTemplateText)
+ })
it('should allow template property to be an empty string', () => {
scope.fields = [
{template: ''},
- ];
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal('');
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal('')
+ })
it('should allow templateUrl property to be a function', () => {
scope.fields = [
{
templateUrl: function(options) {
- expect(options).to.eq(scope.fields[0]);
- return 'templateUrlTest.html';
- }
- }
- ];
+ expect(options).to.eq(scope.fields[0])
+ return 'templateUrlTest.html'
+ },
+ },
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal(expectedTemplateText);
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal(expectedTemplateText)
+ })
it('should allow templateUrl property to be a function that returns a promise', () => {
scope.fields = [
{
templateUrl: function(options) {
- expect(options).to.eq(scope.fields[0]);
- return $q.when('templateUrlTest.html');
- }
- }
- ];
+ expect(options).to.eq(scope.fields[0])
+ return $q.when('templateUrlTest.html')
+ },
+ },
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal(expectedTemplateText);
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal(expectedTemplateText)
+ })
it('should allow templateUrl property to be a string', () => {
scope.fields = [
- {templateUrl: 'templateUrlTest.html'}
- ];
+ {templateUrl: 'templateUrlTest.html'},
+ ]
- const el = compileAndDigest();
+ const el = compileAndDigest()
- const fieldEl = angular.element(el[0].querySelector('.formly-field'));
- expect(fieldEl.text()).to.equal(expectedTemplateText);
- });
- });
+ const fieldEl = angular.element(el[0].querySelector('.formly-field'))
+ expect(fieldEl.text()).to.equal(expectedTemplateText)
+ })
+ })
describe(`defaultValue`, () => {
- const key = 'foo';
- const defaultValue = '~=[,,_,,]:3';
+ const key = 'foo'
+ const defaultValue = '~=[,,_,,]:3'
beforeEach(() => {
scope.fields = [
- {template: input, key, defaultValue}
- ];
- scope.model = {};
- });
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+ })
it(`should default the model's value to the specified value if it is not defined`, () => {
- compileAndDigest();
- expect(scope.model[key]).to.equal(defaultValue);
- });
+ compileAndDigest()
+ expect(scope.model[key]).to.equal(defaultValue)
+ })
it(`should not have a problem if the model starts out as undefined`, () => {
- scope.model = undefined;
- compileAndDigest();
- expect(scope.model[key]).to.equal(defaultValue);
- });
+ scope.model = undefined
+ compileAndDigest()
+ expect(scope.model[key]).to.equal(defaultValue)
+ })
it(`should not change the model's value if the specified value is defined`, () => {
- const presetValue = 'ಠ_ರೃ';
- scope.model[key] = presetValue;
+ const presetValue = 'ಠ_ರೃ'
+ scope.model[key] = presetValue
- compileAndDigest();
- expect(scope.model[key]).to.equal(presetValue);
- });
+ compileAndDigest()
+ expect(scope.model[key]).to.equal(presetValue)
+ })
it(`should be exactly equal to a non-primative`, () => {
- const complexDefaultValue = {foo: 'bar'};
- scope.fields[0].defaultValue = complexDefaultValue;
+ const complexDefaultValue = {foo: 'bar'}
+ scope.fields[0].defaultValue = complexDefaultValue
- compileAndDigest();
- expect(scope.model[key]).to.eq(complexDefaultValue);
- });
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(complexDefaultValue)
+ })
it(`should be set even if the defaultValue is falsy`, () => {
- const falsyValue = 0;
- scope.fields[0].defaultValue = falsyValue;
+ const falsyValue = 0
+ scope.fields[0].defaultValue = falsyValue
- compileAndDigest();
- expect(scope.model[key]).to.eq(falsyValue);
- });
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(falsyValue)
+ })
it(`should be set as the initialValue`, () => {
- compileAndDigest();
+ compileAndDigest()
+
+ expect(scope.fields[0].initialValue).to.eq(defaultValue)
+ })
- expect(scope.fields[0].initialValue).to.eq(defaultValue);
- });
+ it(`should be set if the key is 0`, () => {
+ scope.fields[0].key = 0
+ compileAndDigest()
+
+ expect(scope.fields[0].initialValue).to.eq(defaultValue)
+ })
describe(`nested keys`, () => {
- const nestedObject = 'foo.bar';
- const nestedArray = 'baz[0]';
+ const nestedObject = 'foo.bar'
+ const nestedArray = 'baz[0]'
beforeEach(() => {
- const firstField = scope.fields[0];
- firstField.key = nestedObject;
+ const firstField = scope.fields[0]
+ firstField.key = nestedObject
- const secondField = {template: input, key: nestedArray, defaultValue};
- scope.fields.push(secondField);
- });
+ const secondField = {template: input, key: nestedArray, defaultValue}
+ scope.fields.push(secondField)
+ })
it(`should set the default value for nested keys`, () => {
- compileAndDigest();
- expect(scope.model.foo.bar).to.equal(defaultValue);
- expect(scope.model.baz[0]).to.equal(defaultValue);
- });
- });
- });
+ compileAndDigest()
+ expect(scope.model.foo.bar).to.equal(defaultValue)
+ expect(scope.model.baz[0]).to.equal(defaultValue)
+ })
+ })
+ })
+
+ describe('getterSetters', () => {
+ it('should get and set values when key is all alpha', () => {
+ const key = 'foo'
+ const defaultValue = 'bar'
+
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should get and set values when key is all numeric', () => {
+ const key = '1333'
+ const defaultValue = 'bar'
+
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should get and set values when key is 0', () => {
+ const key = 0
+ const defaultValue = 'bar'
+
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should handle arrays properly when formlyConfig.extras.parseKeyArrays is set', () => {
+ const key = 'foo[0]'
+ const defaultValue = 'bar'
+
+ formlyConfig.extras.parseKeyArrays = true
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model.foo).to.be.instanceof(Array)
+ })
+
+ it('should get and set values when key is alpha numeric with alpha first', () => {
+ const key = 'A1'
+ const defaultValue = 'bar'
+
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should get and set values when key is alpha numeric with numeric first', () => {
+ const key = '1A'
+ const defaultValue = 'bar'
+
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should work with dashes in the key', () => {
+ const key = 'address-1st-line'
+ const defaultValue = 'baz'
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should work with dashes and numerics in the key', () => {
+ const key = 'b141c66a-2857-4196-847b-b2096fa6170d'
+ const defaultValue = 'baz'
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model[key]).to.eq(defaultValue)
+ })
+
+ it('should work with nested keys with numbers in the key', () => {
+ const key = 'foo3bar.baz4foobar'
+ const defaultValue = 'baz'
+ scope.fields = [
+ {template: input, key, defaultValue},
+ ]
+ scope.model = {}
+
+ compileAndDigest()
+ expect(scope.model.foo3bar.baz4foobar).to.eq(defaultValue)
+ })
+ })
describe(`id property`, () => {
it(`should default to a semi-random id that you cannot rely on and don't have to think about`, () => {
- scope.fields = [getNewField()];
- compileAndDigest();
- const fieldNode = getFieldNgModelNode();
- expect(field.id).to.eq(fieldNode.id);
- expect(fieldNode.id).to.match(/^formly_\d+_template_\d+_\d+$/);
- expect(fieldNode.id).to.eq(fieldNode.getAttribute('name'));
- });
+ scope.fields = [getNewField()]
+ compileAndDigest()
+ const fieldNode = getFieldNgModelNode()
+ expect(field.id).to.eq(fieldNode.id)
+ expect(fieldNode.id).to.match(/^formly_\d+_template_\d+_\d+$/)
+ expect(fieldNode.id).to.eq(fieldNode.getAttribute('name'))
+ })
it(`should allow you to specify a custom id if you want to`, () => {
- scope.fields = [getNewField({id: 'ᕕ( ᐛ )ᕗ'})];
- compileAndDigest();
- const fieldNode = getFieldNgModelNode();
- expect(field.id).to.eq(fieldNode.id);
- expect(fieldNode.id).to.eq('ᕕ( ᐛ )ᕗ');
- expect(fieldNode.id).to.eq(fieldNode.getAttribute('name'));
- });
- });
+ scope.fields = [getNewField({id: 'ᕕ( ᐛ )ᕗ'})]
+ compileAndDigest()
+ const fieldNode = getFieldNgModelNode()
+ expect(field.id).to.eq(fieldNode.id)
+ expect(fieldNode.id).to.eq('ᕕ( ᐛ )ᕗ')
+ expect(fieldNode.id).to.eq(fieldNode.getAttribute('name'))
+ })
+ })
describe(`modelOptions property`, () => {
it(`should be able to handle modelOptions with debouce as a number`, () => {
- scope.fields = [getNewField({modelOptions: {debounce: 500}})];
- compileAndDigest();
- const fieldNode = getFieldNgModelNode();
- expect(fieldNode.getAttribute('ng-model-options')).to.exist;
- });
+ scope.fields = [getNewField({modelOptions: {debounce: 500}})]
+ compileAndDigest()
+ const fieldNode = getFieldNgModelNode()
+ expect(fieldNode.getAttribute('ng-model-options')).to.exist
+ })
it(`should be able to compile modelOptions with debounce as an object of numbers`, () => {
- scope.fields = [getNewField({modelOptions: {debounce: {blur: 500, default: 0}}})];
- compileAndDigest();
- const fieldNode = getFieldNgModelNode();
- expect(fieldNode.getAttribute('ng-model-options')).to.exist;
- });
+ scope.fields = [getNewField({modelOptions: {debounce: {blur: 500, default: 0}}})]
+ compileAndDigest()
+ const fieldNode = getFieldNgModelNode()
+ expect(fieldNode.getAttribute('ng-model-options')).to.exist
+ })
it(`should throw an error when modelOptions with debounce as a string`, () => {
- scope.fields = [getNewField({modelOptions: {debounce: 'foo'}})];
- expect(() => compileAndDigest()).to.throw();
- });
+ scope.fields = [getNewField({modelOptions: {debounce: 'foo'}})]
+ expect(() => compileAndDigest()).to.throw()
+ })
it(`should throw an error when modelOptions with debounce as an object of strings`, () => {
- scope.fields = [getNewField({modelOptions: {debounce: {blur: 'foo', default: 'bar'}}})];
- expect(() => compileAndDigest()).to.throw();
- });
+ scope.fields = [getNewField({modelOptions: {debounce: {blur: 'foo', default: 'bar'}}})]
+ expect(() => compileAndDigest()).to.throw()
+ })
describe(`value function`, () => {
- let value;
+ let value
it(`should be overrideable via value option`, () => {
- compileDigestAndSetValueFunction({value: customGetterSetter});
- expect(value).to.eq(customGetterSetter);
+ compileDigestAndSetValueFunction({value: customGetterSetter})
+ expect(value).to.eq(customGetterSetter)
function customGetterSetter() {
}
- });
+ })
it(`should be a getter/setter`, () => {
- compileDigestAndSetValueFunction();
- expect(value()).to.eq(undefined);
- expect(value('foo')).to.eq('foo');
- expect(value()).to.eq('foo');
- });
+ compileDigestAndSetValueFunction()
+ expect(value()).to.eq(undefined)
+ expect(value('foo')).to.eq('foo')
+ expect(value()).to.eq('foo')
+ })
it(`should not throw an error when the model is undefined`, inject(($rootScope) => {
const formlyField = `
-
`;
- scope = $rootScope.$new();
+ `
+ scope = $rootScope.$new()
_.assign(scope, {
field: getNewField(),
- model: undefined // <-- this is the key
- });
- el = $compile(formlyField)(scope);
- scope.$digest();
- expect(() => scope.field.value()).to.not.throw();
- }));
+ model: undefined, // <-- this is the key
+ })
+ el = $compile(formlyField)(scope)
+ scope.$digest()
+ expect(() => scope.field.value()).to.not.throw()
+ }))
function compileDigestAndSetValueFunction(fieldOverrides) {
- scope.fields = [getNewField(_.merge({modelOptions: {getterSetter: true}}, fieldOverrides))];
- compileAndDigest();
- value = getIsolateScope().options.value;
+ scope.fields = [getNewField(_.merge({modelOptions: {getterSetter: true}}, fieldOverrides))]
+ compileAndDigest()
+ value = getIsolateScope().options.value
}
- });
+ })
- });
+ })
describe(`type apiCheck`, () => {
- let inputType;
- const type = 'input';
+ let inputType
+ const type = 'input'
beforeEach(() => {
inputType = formlyConfig.setType({
name: type,
@@ -616,57 +740,57 @@ describe('formly-field', function() {
return {
templateOptions: {
label: check.string,
- className: check.string
- }
- };
+ className: check.string,
+ },
+ }
},
apiCheckInstance: apiCheck({
- output: {prefix: 'custom-api-check'}
+ output: {prefix: 'custom-api-check'},
}),
apiCheckOptions: {
- url: 'http://example.com/some-custom-url'
- }
- });
- scope.model = {};
- });
+ url: 'http://example.com/some-custom-url',
+ },
+ })
+ scope.model = {}
+ })
it(`should default to the built-in formlyApiCheck`, inject((formlyApiCheck) => {
const type = formlyConfig.setType({
name: 'someOtherType',
template: ' ',
- apiCheck: sinon.spy()
- });
- scope.fields = [{type: 'someOtherType'}];
- compileAndDigest();
- expect(type.apiCheck).to.have.been.calledWith(formlyApiCheck);
- }));
+ apiCheck: sinon.spy(),
+ })
+ scope.fields = [{type: 'someOtherType'}]
+ compileAndDigest()
+ expect(type.apiCheck).to.have.been.calledWith(formlyApiCheck)
+ }))
it(`should not warn if everything's fine`, () => {
scope.fields = [
- {type, templateOptions: {label: 'string', className: 'string'}}
- ];
- shouldNotWarn(compileAndDigest);
- });
+ {type, templateOptions: {label: 'string', className: 'string'}},
+ ]
+ shouldNotWarn(compileAndDigest)
+ })
it(`should warn if everything's not fine`, () => {
scope.fields = [
- {type, templateOptions: {label: 'string'}}
- ];
+ {type, templateOptions: {label: 'string'}},
+ ]
shouldWarn(
/custom-api-check formly-field type input for property templateOptions apiCheck failed.*?className.*?some-custom-url/,
compileAndDigest
- );
- });
+ )
+ })
it(`should throw if the apiCheckFunction is set to "throw" and everything's not fine`, () => {
- formlyConfig.getType(type).apiCheckFunction = 'throw';
+ formlyConfig.getType(type).apiCheckFunction = 'throw'
scope.fields = [
- {type, templateOptions: {label: 'string'}}
- ];
+ {type, templateOptions: {label: 'string'}},
+ ]
expect(compileAndDigest).to.throw(
/custom-api-check formly-field type input for property templateOptions apiCheck failed.*?.className.*?some-custom-url/
- );
- });
+ )
+ })
it(`should work with wrappers as well`, () => {
formlyConfig.setWrapper({
@@ -675,75 +799,75 @@ describe('formly-field', function() {
apiCheck(check) {
return {
templateOptions: {
- foo: check.bool
- }
- };
+ foo: check.bool,
+ },
+ }
},
apiCheckInstance: apiCheck({output: {prefix: 'my own'}}),
- apiCheckOptions: {prefix: 'options prefix'}
- });
+ apiCheckOptions: {prefix: 'options prefix'},
+ })
scope.fields = [
- {type, wrapper: 'mywrapper', templateOptions: {label: 'string', className: 'string'}}
- ];
+ {type, wrapper: 'mywrapper', templateOptions: {label: 'string', className: 'string'}},
+ ]
shouldWarn(
/my own options prefix apiCheck failed/,
compileAndDigest
- );
- });
+ )
+ })
describe(`apiCheckInstance`, () => {
describe(`disabled`, () => {
it(`should not do anything if the given instance is disabled`, () => {
- inputType.apiCheckInstance.config.disabled = true;
+ inputType.apiCheckInstance.config.disabled = true
scope.fields = [
- {type, templateOptions: {label: 'string'}}
- ];
- shouldNotWarn(compileAndDigest);
- });
+ {type, templateOptions: {label: 'string'}},
+ ]
+ shouldNotWarn(compileAndDigest)
+ })
it(`should not do anything if no instance is provided and the formly instance is disabled`, inject((formlyApiCheck) => {
- formlyApiCheck.config.disabled = true;
+ formlyApiCheck.config.disabled = true
formlyConfig.setType({
name: 'someOtherType',
template: ' ',
- apiCheck: checker => ({data: {foo: checker.bool}})
- });
- scope.fields = [{type: 'someOtherType'}];
- shouldNotWarn(compileAndDigest);
- formlyApiCheck.config.disabled = false;
- }));
+ apiCheck: checker => ({data: {foo: checker.bool}}),
+ })
+ scope.fields = [{type: 'someOtherType'}]
+ shouldNotWarn(compileAndDigest)
+ formlyApiCheck.config.disabled = false
+ }))
it(`should not do anything if the global instance is disabled`, () => {
- apiCheck.globalConfig.disabled = true;
+ apiCheck.globalConfig.disabled = true
scope.fields = [
- {type, templateOptions: {label: 'string'}}
- ];
- shouldNotWarn(compileAndDigest);
- apiCheck.globalConfig.disabled = false;
- });
- });
+ {type, templateOptions: {label: 'string'}},
+ ]
+ shouldNotWarn(compileAndDigest)
+ apiCheck.globalConfig.disabled = false
+ })
+ })
describe(`formlyConfig.extras.apiCheckInstance`, () => {
it(`should default to this instance when specified and no specific type instance is specified`, () => {
const globalApiCheckInstance = apiCheck({
- output: {prefix: 'custom-api-check'}
- });
- const warnSpy = sinon.spy(globalApiCheckInstance, 'warn');
- formlyConfig.extras.apiCheckInstance = globalApiCheckInstance;
- delete inputType.apiCheckInstance;
+ output: {prefix: 'custom-api-check'},
+ })
+ const warnSpy = sinon.spy(globalApiCheckInstance, 'warn')
+ formlyConfig.extras.apiCheckInstance = globalApiCheckInstance
+ delete inputType.apiCheckInstance
scope.fields = [
- {type, templateOptions: {label: 'string', className: 'valid'}}
- ];
- compileAndDigest();
- expect(warnSpy).to.have.been.calledOnce;
- });
- });
- });
+ {type, templateOptions: {label: 'string', className: 'valid'}},
+ ]
+ compileAndDigest()
+ expect(warnSpy).to.have.been.calledOnce
+ })
+ })
+ })
describe(`extended scenario`, () => {
- let childType, pristineOptions;
+ let childType, pristineOptions
beforeEach(() => {
- sinon.spy(inputType, 'apiCheck');
+ sinon.spy(inputType, 'apiCheck')
childType = formlyConfig.setType({
name: type + 'Child',
extends: type,
@@ -751,51 +875,51 @@ describe('formly-field', function() {
apiCheck(check) {
return {
data: {
- foo: check.string
- }
- };
+ foo: check.string,
+ },
+ }
},
apiCheckFunction: 'throw',
apiCheckInstance: apiCheck({
- output: {suffix: 'my own'}
+ output: {suffix: 'my own'},
}),
- apiCheckOptions: {url: 'http://other-url.example.com', prefix: type + 'Child type checker'}
- });
+ apiCheckOptions: {url: 'http://other-url.example.com', prefix: type + 'Child type checker'},
+ })
- sinon.spy(childType, 'apiCheck');
+ sinon.spy(childType, 'apiCheck')
- pristineOptions = {type: type + 'Child', templateOptions: {label: 'foo', className: 'bar'}, data: {foo: 'bar'}};
- });
+ pristineOptions = {type: type + 'Child', templateOptions: {label: 'foo', className: 'bar'}, data: {foo: 'bar'}}
+ })
it(`should pass if everything is ok`, () => {
- compileDigestAndMatchError();
- expect(childType.apiCheck).to.have.been.calledWith(childType.apiCheckInstance);
- expect(inputType.apiCheck).to.have.been.calledWith(inputType.apiCheckInstance);
- });
+ compileDigestAndMatchError()
+ expect(childType.apiCheck).to.have.been.calledWith(childType.apiCheckInstance)
+ expect(inputType.apiCheck).to.have.been.calledWith(inputType.apiCheckInstance)
+ })
it(`should throw if the child has a problem`, () => {
compileDigestAndMatchError(
{data: {foo: false}},
/inputChild type checker apiCheck failed.*?`foo`.*?`String`.*?my own.*?other-url\.example\.com/
- );
- });
+ )
+ })
it(`should invoke the apiCheck for all extended types if an error is not thrown`, () => {
- childType.apiCheckFunction = 'warn';
- compileDigestAndMatchError();
- expect(childType.apiCheck).to.have.been.calledWith(childType.apiCheckInstance);
- expect(inputType.apiCheck).to.have.been.calledWith(inputType.apiCheckInstance);
- });
+ childType.apiCheckFunction = 'warn'
+ compileDigestAndMatchError()
+ expect(childType.apiCheck).to.have.been.calledWith(childType.apiCheckInstance)
+ expect(inputType.apiCheck).to.have.been.calledWith(inputType.apiCheckInstance)
+ })
function compileDigestAndMatchError(fieldOverrides, error) {
- scope.fields = [_.merge(pristineOptions, fieldOverrides)];
+ scope.fields = [_.merge(pristineOptions, fieldOverrides)]
if (error) {
- expect(compileAndDigest).to.throw(error);
+ expect(compileAndDigest).to.throw(error)
} else {
- expect(compileAndDigest).to.not.throw();
+ expect(compileAndDigest).to.not.throw()
}
}
- });
+ })
it(`should have good default options`, () => {
formlyConfig.setType({
@@ -805,27 +929,27 @@ describe('formly-field', function() {
return {
templateOptions: {
label: check.string,
- className: check.string
- }
- };
+ className: check.string,
+ },
+ }
},
apiCheckInstance: apiCheck({
- output: {prefix: 'custom-api-check'}
- })
- });
+ output: {prefix: 'custom-api-check'},
+ }),
+ })
scope.fields = [
- {type: 'someType', templateOptions: {label: 'string'}}
- ];
+ {type: 'someType', templateOptions: {label: 'string'}},
+ ]
shouldWarn(
/custom-api-check formly-field type someType for property templateOptions apiCheck failed.*?Required.*?className/,
compileAndDigest
- );
- });
- });
+ )
+ })
+ })
describe(`wrapper apiCheck`, () => {
- const name = 'input';
- const wrapper = name;
+ const name = 'input'
+ const wrapper = name
beforeEach(() => {
formlyConfig.setWrapper({
name,
@@ -834,63 +958,63 @@ describe('formly-field', function() {
return {
templateOptions: {
label: check.string,
- className: check.string
- }
- };
+ className: check.string,
+ },
+ }
},
apiCheckInstance: apiCheck({
- output: {prefix: 'custom-api-check'}
- })
- });
- scope.model = {};
+ output: {prefix: 'custom-api-check'},
+ }),
+ })
+ scope.model = {}
scope.fields = [
- {template: input, wrapper, templateOptions: {}}
- ];
- });
+ {template: input, wrapper, templateOptions: {}},
+ ]
+ })
it(`should not warn if everything's fine`, () => {
- scope.fields[0].templateOptions = {label: 'string', className: 'string'};
- shouldNotWarn(compileAndDigest);
- });
+ scope.fields[0].templateOptions = {label: 'string', className: 'string'}
+ shouldNotWarn(compileAndDigest)
+ })
it(`should warn if everything's not fine`, () => {
- scope.fields[0].templateOptions = {label: 'string'};
- shouldWarn(/custom-api-check.*?formly-field(.|\n)*?className/, compileAndDigest);
- });
+ scope.fields[0].templateOptions = {label: 'string'}
+ shouldWarn(/custom-api-check.*?formly-field(.|\n)*?className/, compileAndDigest)
+ })
it(`should throw if the apiCheckFunction is set to "throw" and everything's not fine`, () => {
- formlyConfig.getWrapper(name).apiCheckFunction = 'throw';
- scope.fields[0].templateOptions = {label: 'string'};
- expect(compileAndDigest).to.throw(/custom-api-check.*?formly-field(.|\n)*?className/);
- });
- });
+ formlyConfig.getWrapper(name).apiCheckFunction = 'throw'
+ scope.fields[0].templateOptions = {label: 'string'}
+ expect(compileAndDigest).to.throw(/custom-api-check.*?formly-field(.|\n)*?className/)
+ })
+ })
describe(`formControl`, () => {
beforeEach(() => {
- scope.fields = [{template: input}];
- });
+ scope.fields = [{template: input}]
+ })
it(`should be placed onto field's options`, () => {
- compileAndDigest();
- expect(field.formControl).to.exist;
- });
+ compileAndDigest()
+ expect(field.formControl).to.exist
+ })
it(`should be placed onto the isolate scope for the formly-field`, () => {
- compileAndDigest();
- expect(isolateScope.fc).to.exist;
- });
+ compileAndDigest()
+ expect(isolateScope.fc).to.exist
+ })
it(`should add a formControl even on a field with an ng-if on the ng-model`, () => {
- const template = ' ';
- const field = {template, templateOptions: {if: false}};
- scope.fields = [field];
- compileAndDigest();
- expect(isolateScope.fc).to.not.exist;
- field.templateOptions.if = true;
- scope.$digest();
- expect(isolateScope.fc).to.exist;
- });
+ const template = ' '
+ const field = {template, templateOptions: {if: false}}
+ scope.fields = [field]
+ compileAndDigest()
+ expect(isolateScope.fc).to.not.exist
+ field.templateOptions.if = true
+ scope.$digest()
+ expect(isolateScope.fc).to.exist
+ })
it(`should be used to add the formControl watcher if set to false even if there is no ng-model`, () => {
const radioTemplate = `
@@ -906,259 +1030,259 @@ describe('formly-field', function() {
- `;
+ `
scope.fields = [
{
template: radioTemplate,
templateOptions: {
- options: [{name: 'Name', value: 'name'}]
- }
- }
- ];
- compileAndDigest();
- expect(isolateScope.fc).to.exist;
- });
+ options: [{name: 'Name', value: 'name'}],
+ },
+ },
+ ]
+ compileAndDigest()
+ expect(isolateScope.fc).to.exist
+ })
describe(`noFormControl`, () => {
it(`should skip adding the formControl if set to true`, () => {
- scope.fields = [{template: input, noFormControl: true}];
- compileAndDigest();
- expect(isolateScope.fc).to.not.exist;
- });
+ scope.fields = [{template: input, noFormControl: true}]
+ compileAndDigest()
+ expect(isolateScope.fc).to.not.exist
+ })
- });
+ })
describe(`name`, () => {
it(`should be almost random`, () => {
- compileAndDigest();
- expect(field.formControl.$name).to.match(/formly_\d+_template_.*?_\d+/);
- });
+ compileAndDigest()
+ expect(field.formControl.$name).to.match(/formly_\d+_template_.*?_\d+/)
+ })
it(`should be overrideable when a different name is specified`, () => {
- scope.fields[0].template = ` `;
- compileAndDigest();
- makeNameExpectations('myCustomName');
- });
+ scope.fields[0].template = ` `
+ compileAndDigest()
+ makeNameExpectations('myCustomName')
+ })
it(`should handle interpolated names`, () => {
- scope.fields[0].template = ` `;
- compileAndDigest();
- makeNameExpectations('myCustomName');
- });
+ scope.fields[0].template = ` `
+ compileAndDigest()
+ makeNameExpectations('myCustomName')
+ })
function makeNameExpectations(name) {
- expect(field.formControl).to.exist;
- expect(isolateScope.fc).to.exist;
- expect(field.formControl.$name).to.eq(name);
- expect(scope.theForm).to.have.property(name);
+ expect(field.formControl).to.exist
+ expect(isolateScope.fc).to.exist
+ expect(field.formControl.$name).to.eq(name)
+ expect(scope.theForm).to.have.property(name)
}
- });
+ })
describe(`multiple ng-models`, () => {
it(`should be an array`, () => {
scope.fields = [{
- template: multiNgModelField
- }];
+ template: multiNgModelField,
+ }]
- compileAndDigest();
- expect(isolateScope.fc).to.be.instanceof(Array);
- });
- });
- });
+ compileAndDigest()
+ expect(isolateScope.fc).to.be.instanceof(Array)
+ })
+ })
+ })
describe(`parsers/formatters`, () => {
describe(`parsers`, () => {
it(`should be merged in the right order`, () => {
- testParsersOrFormatters('parsers');
- });
+ testParsersOrFormatters('parsers')
+ })
it(`should handle a formlyExpression as a string`, () => {
scope.fields = [getNewField({
key: 'myKey',
parsers: ['$viewValue + options.data.extraThing'],
- data: {extraThing: ' boo!'}
- })];
- compileAndDigest();
- const ctrl = getNgModelCtrl();
- expect(ctrl.$parsers).to.have.length(1);
- ctrl.$setViewValue('hello!');
- expect(scope.model.myKey).to.equal('hello! boo!');
- });
- });
+ data: {extraThing: ' boo!'},
+ })]
+ compileAndDigest()
+ const ctrl = getNgModelCtrl()
+ expect(ctrl.$parsers).to.have.length(1)
+ ctrl.$setViewValue('hello!')
+ expect(scope.model.myKey).to.equal('hello! boo!')
+ })
+ })
describe(`formatters`, () => {
it(`should be merged in the right order`, () => {
- testParsersOrFormatters('formatters');
- });
+ testParsersOrFormatters('formatters')
+ })
it(`should handle a formlyExpression as a string`, () => {
scope.fields = [getNewField({
key: 'myKey',
formatters: ['$viewValue + options.data.extraThing'],
- data: {extraThing: ' boo!'}
- })];
- compileAndDigest();
- scope.model.myKey = 'hello!';
- scope.$digest();
- const ctrl = getNgModelCtrl();
- expect(ctrl.$formatters).to.have.length(2); // ngModel adds one
- expect(ctrl.$viewValue).to.equal('hello! boo!');
- });
+ data: {extraThing: ' boo!'},
+ })]
+ compileAndDigest()
+ scope.model.myKey = 'hello!'
+ scope.$digest()
+ const ctrl = getNgModelCtrl()
+ expect(ctrl.$formatters).to.have.length(2) // ngModel adds one
+ expect(ctrl.$viewValue).to.equal('hello! boo!')
+ })
it(`should format a model value right from the start and the controller should still be pristine`, () => {
- scope.model = {myKey: 'hello'};
+ scope.model = {myKey: 'hello'}
scope.fields = [getNewField({
key: 'myKey',
- formatters: ['"!" + $viewValue + "!"']
- })];
- compileAndDigest();
+ formatters: ['"!" + $viewValue + "!"'],
+ })]
+ compileAndDigest()
- const ctrl = getNgModelCtrl();
+ const ctrl = getNgModelCtrl()
- expect(ctrl.$viewValue).to.equal('!hello!');
- expect(ctrl.$dirty).to.equal(false);
- expect(ctrl.$pristine).to.equal(true);
- });
+ expect(ctrl.$viewValue).to.equal('!hello!')
+ expect(ctrl.$dirty).to.equal(false)
+ expect(ctrl.$pristine).to.equal(true)
+ })
it(`should format a model value on initilization and keep the form state dirty if it was already dirty`, () => {
- scope.model = {myKey: 'hello'};
+ scope.model = {myKey: 'hello'}
scope.fields = [getNewField({
key: 'myKey',
- formatters: ['"!" + $viewValue + "!"']
- })];
- compileAndDigest();
- scope.theForm.$setDirty();
+ formatters: ['"!" + $viewValue + "!"'],
+ })]
+ compileAndDigest()
+ scope.theForm.$setDirty()
- const ctrl = getNgModelCtrl();
+ const ctrl = getNgModelCtrl()
- expect(ctrl.$viewValue).to.equal('!hello!');
- expect(scope.theForm.$dirty).to.equal(true);
+ expect(ctrl.$viewValue).to.equal('!hello!')
+ expect(scope.theForm.$dirty).to.equal(true)
- });
+ })
it(`should format a model value on initilization and keep the form state pristine if it was already pristine`, () => {
- scope.model = {myKey: 'hello'};
+ scope.model = {myKey: 'hello'}
scope.fields = [getNewField({
key: 'myKey',
- formatters: ['"!" + $viewValue + "!"']
- })];
- compileAndDigest();
+ formatters: ['"!" + $viewValue + "!"'],
+ })]
+ compileAndDigest()
- const ctrl = getNgModelCtrl();
+ const ctrl = getNgModelCtrl()
- expect(ctrl.$viewValue).to.equal('!hello!');
- expect(scope.theForm.$pristine).to.equal(true);
+ expect(ctrl.$viewValue).to.equal('!hello!')
+ expect(scope.theForm.$pristine).to.equal(true)
- });
+ })
it.skip(`should handle multiple form controllers when formatting a model value right from the start`, () => {
scope.model = {
multiNgModel: {
start: 'start',
- stop: 'stop'
- }
- };
+ stop: 'stop',
+ },
+ }
const field = getNewField({
key: 'multiNgModel',
template: multiNgModelField,
- formatters: ['"!" + $viewValue + "!"']
- });
- scope.fields = [field];
+ formatters: ['"!" + $viewValue + "!"'],
+ })
+ scope.fields = [field]
- compileAndDigest();
+ compileAndDigest()
- const ctrl1 = field.formControl[0];
- const ctrl2 = field.formControl[1];
+ const ctrl1 = field.formControl[0]
+ const ctrl2 = field.formControl[1]
- expect(ctrl1.$viewValue).to.equal('!start!');
- expect(ctrl2.$viewValue).to.equal('!stop!');
- });
+ expect(ctrl1.$viewValue).to.equal('!start!')
+ expect(ctrl2.$viewValue).to.equal('!stop!')
+ })
- });
+ })
function testParsersOrFormatters(which) {
- let originalThingProp = 'originalParser';
+ let originalThingProp = 'originalParser'
if (which === 'formatters') {
- originalThingProp = 'originalFormatter';
+ originalThingProp = 'originalFormatter'
}
const parent1Thing1 = sinon.spy(function parent1Thing1() {
- });
+ })
const parent1Thing2 = sinon.spy(function parent1Thing2() {
- });
+ })
const parent2Thing1 = sinon.spy(function parent2Thing1() {
- });
+ })
const parent2Thing2 = sinon.spy(function parent2Thing2() {
- });
+ })
const childThing1 = sinon.spy(function childThing1() {
- });
+ })
const childThing2 = sinon.spy(function childThing2() {
- });
+ })
const optionType1Thing1 = sinon.spy(function optionType1Thing1() {
- });
+ })
const optionType1Thing2 = sinon.spy(function optionType1Thing2() {
- });
+ })
const optionType2Thing1 = sinon.spy(function optionType2Thing1() {
- });
+ })
const optionType2Thing2 = sinon.spy(function optionType2Thing2() {
- });
+ })
const fieldThing1 = sinon.spy(function fieldThing1() {
- });
+ })
const fieldThing2 = sinon.spy(function fieldThing2() {
- });
+ })
formlyConfig.setType({
name: 'parent1',
defaultOptions: {
- [which]: [parent1Thing1, parent1Thing2]
- }
- });
+ [which]: [parent1Thing1, parent1Thing2],
+ },
+ })
formlyConfig.setType({
name: 'parent2',
defaultOptions: {
- [which]: [parent2Thing1, parent2Thing2]
- }
- });
+ [which]: [parent2Thing1, parent2Thing2],
+ },
+ })
formlyConfig.setType({
name: 'child',
template: ' ',
extends: 'parent1', // <-- note this!
defaultOptions: {
- [which]: [childThing1, childThing2]
- }
- });
+ [which]: [childThing1, childThing2],
+ },
+ })
formlyConfig.setType({
name: 'optionType1',
extends: 'parent2', // <-- note this!
defaultOptions: {
- [which]: [optionType1Thing1, optionType1Thing2]
- }
- });
+ [which]: [optionType1Thing1, optionType1Thing2],
+ },
+ })
formlyConfig.setType({
name: 'optionType2',
defaultOptions: {
- [which]: [optionType2Thing1, optionType2Thing2]
- }
- });
+ [which]: [optionType2Thing1, optionType2Thing2],
+ },
+ })
scope.fields = [
{
type: 'child',
optionsTypes: ['optionType1', 'optionType2'],
- [which]: [fieldThing1, fieldThing2]
- }
- ];
+ [which]: [fieldThing1, fieldThing2],
+ },
+ ]
- compileAndDigest();
- const ctrl = getNgModelCtrl();
- const originalThings = ctrl['$' + which].map(thing => thing[originalThingProp]);
+ compileAndDigest()
+ const ctrl = getNgModelCtrl()
+ const originalThings = ctrl['$' + which].map(thing => thing[originalThingProp])
if (which === 'formatters') {
// all ngModelControllers have a default formatter, remove that from the originalThings for our test
- originalThings.shift();
+ originalThings.shift()
}
expect(originalThings).to.eql([
parent1Thing1, parent1Thing2,
@@ -1166,354 +1290,504 @@ describe('formly-field', function() {
parent2Thing1, parent2Thing2,
optionType1Thing1, optionType1Thing2,
optionType2Thing1, optionType2Thing2,
- fieldThing1, fieldThing2
- ]);
+ fieldThing1, fieldThing2,
+ ])
}
- });
+ })
describe(`link`, () => {
describe(`addClasses`, () => {
it(`should add the type class`, () => {
formlyConfig.setType({
name: 'input',
- template: input
- });
+ template: input,
+ })
- scope.fields = [{type: 'input'}];
+ scope.fields = [{type: 'input'}]
- compileAndDigest();
- expect(el[0].querySelector('[formly-field].formly-field-input')).to.exist;
- });
+ compileAndDigest()
+ expect(el[0].querySelector('[formly-field].formly-field-input')).to.exist
+ })
it(`should add the className class`, () => {
- scope.fields = [getNewField({className: 'classy'}), getNewField({className: 'very-classy'})];
- compileAndDigest();
- expect(el[0].querySelector('[formly-field].classy')).to.exist;
- expect(el[0].querySelector('[formly-field].very-classy')).to.exist;
- });
- });
- });
+ scope.fields = [getNewField({className: 'classy'}), getNewField({className: 'very-classy'})]
+ compileAndDigest()
+ expect(el[0].querySelector('[formly-field].classy')).to.exist
+ expect(el[0].querySelector('[formly-field].very-classy')).to.exist
+ })
+ })
+ })
describe(`elementAttributes`, () => {
it(`should allow fields to have attributes which will be applied to the [formly-field]`, () => {
- scope.fields = [getNewField({elementAttributes: {foo: 'bar', baz: 'eggs'}})];
- compileAndDigest();
- expect(el[0].querySelector('[formly-field][foo=bar][baz=eggs]')).to.exist;
- });
+ scope.fields = [getNewField({elementAttributes: {foo: 'bar', baz: 'eggs'}})]
+ compileAndDigest()
+ expect(el[0].querySelector('[formly-field][foo=bar][baz=eggs]')).to.exist
+ })
it(`should allow fieldGroups to have attributes which will be applied to the ng-form`, () => {
scope.fields = [
- {elementAttributes: {foo: 'bar', baz: 'eggs'}, fieldGroup: [getNewField()]}
- ];
- compileAndDigest();
- expect(el[0].querySelector('ng-form[foo=bar][baz=eggs]')).to.exist;
- });
- });
+ {elementAttributes: {foo: 'bar', baz: 'eggs'}, fieldGroup: [getNewField()]},
+ ]
+ compileAndDigest()
+ expect(el[0].querySelector('ng-form[foo=bar][baz=eggs]')).to.exist
+ })
+ })
describe(`resetModel`, () => {
it(`should reset the form state`, () => {
- const field = getNewField({key: 'foo'});
- scope.fields = [field];
- compileAndDigest();
+ const field = getNewField({key: 'foo'})
+ scope.fields = [field]
+ compileAndDigest()
// initial state
- expect(field.formControl.$dirty).to.be.false;
- expect(field.formControl.$touched).to.be.false;
+ expect(field.formControl.$dirty).to.be.false
+ expect(field.formControl.$touched).to.be.false
+ // modification
+ scope.model.foo = '~=[,,_,,]:3'
+ field.formControl.$setTouched()
+ field.formControl.$setDirty()
+ scope.$digest()
+
+ // expect modification
+ expect(field.formControl.$dirty).to.be.true
+ expect(field.formControl.$touched).to.be.true
+ expect(field.formControl.$modelValue).to.eq('~=[,,_,,]:3')
+
+ // reset state
+ field.resetModel()
+
+ // expect reset
+ expect(field.formControl.$modelValue).to.be.empty
+ expect(field.formControl.$touched).to.be.false
+ expect(field.formControl.$dirty).to.be.false
+ })
+
+ it(`should reset the form state with a deep model`, () => {
+ const field = getNewField({key: 'foo.bar'})
+ scope.fields = [field]
+ compileAndDigest()
+
+ // initial state
+ expect(field.formControl.$dirty).to.be.false
+ expect(field.formControl.$touched).to.be.false
// modification
- scope.model.foo = '~=[,,_,,]:3';
- field.formControl.$setTouched();
- field.formControl.$setDirty();
- scope.$digest();
+ scope.model.foo = {
+ bar: '~=[,,_,,]:3',
+ }
+ field.formControl.$setTouched()
+ field.formControl.$setDirty()
+ scope.$digest()
// expect modification
- expect(field.formControl.$dirty).to.be.true;
- expect(field.formControl.$touched).to.be.true;
- expect(field.formControl.$modelValue).to.eq('~=[,,_,,]:3');
+ expect(field.formControl.$dirty).to.be.true
+ expect(field.formControl.$touched).to.be.true
+ expect(field.formControl.$modelValue).to.eq('~=[,,_,,]:3')
+
+ // Set new initialValue
+ scope.options.updateInitialValue()
+
+ // Modify again
+ scope.model.foo.bar = 'l33t'
+ field.formControl.$setTouched()
+ field.formControl.$setDirty()
+ scope.$digest()
+
+ // expect modification
+ expect(field.formControl.$dirty).to.be.true
+ expect(field.formControl.$touched).to.be.true
+ expect(field.formControl.$modelValue).to.eq('l33t')
// reset state
- field.resetModel();
+ scope.options.resetModel()
// expect reset
- expect(field.formControl.$modelValue).to.be.empty;
- expect(field.formControl.$touched).to.be.false;
- expect(field.formControl.$dirty).to.be.false;
- });
+ expect(field.formControl.$modelValue).to.eq('~=[,,_,,]:3')
+ expect(field.formControl.$touched).to.be.false
+ expect(field.formControl.$dirty).to.be.false
+ })
it(`should reset the form state for an field with multiple ng-models`, () => {
const field = {
key: 'multiNgModel',
- template: multiNgModelField
- };
- scope.fields = [field];
- compileAndDigest();
+ template: multiNgModelField,
+ }
+ scope.fields = [field]
+ compileAndDigest()
// initial state
- expect(field.formControl[0].$dirty).to.be.false;
- expect(field.formControl[0].$touched).to.be.false;
- expect(field.formControl[1].$dirty).to.be.false;
- expect(field.formControl[1].$touched).to.be.false;
+ expect(field.formControl[0].$dirty).to.be.false
+ expect(field.formControl[0].$touched).to.be.false
+ expect(field.formControl[1].$dirty).to.be.false
+ expect(field.formControl[1].$touched).to.be.false
scope.model.multiNgModel = {
start: 0,
- stop: 20
- };
- field.formControl[0].$setDirty();
- field.formControl[0].$setTouched();
- field.formControl[1].$setDirty();
- field.formControl[1].$setTouched();
- scope.$digest();
+ stop: 20,
+ }
+ field.formControl[0].$setDirty()
+ field.formControl[0].$setTouched()
+ field.formControl[1].$setDirty()
+ field.formControl[1].$setTouched()
+ scope.$digest()
// expect modification
- expect(field.formControl[0].$dirty).to.be.true;
- expect(field.formControl[0].$touched).to.be.true;
- expect(field.formControl[0].$modelValue).to.eq(0);
- expect(field.formControl[1].$dirty).to.be.true;
- expect(field.formControl[1].$touched).to.be.true;
- expect(field.formControl[1].$modelValue).to.eq(20);
+ expect(field.formControl[0].$dirty).to.be.true
+ expect(field.formControl[0].$touched).to.be.true
+ expect(field.formControl[0].$modelValue).to.eq(0)
+ expect(field.formControl[1].$dirty).to.be.true
+ expect(field.formControl[1].$touched).to.be.true
+ expect(field.formControl[1].$modelValue).to.eq(20)
// reset state
- field.resetModel();
+ field.resetModel()
// expect reset
- expect(field.formControl[0].$modelValue).to.be.empty;
- expect(field.formControl[0].$touched).to.be.false;
- expect(field.formControl[0].$dirty).to.be.false;
- expect(field.formControl[1].$modelValue).to.be.empty;
- expect(field.formControl[1].$touched).to.be.false;
- expect(field.formControl[1].$dirty).to.be.false;
- });
+ expect(field.formControl[0].$modelValue).to.be.empty
+ expect(field.formControl[0].$touched).to.be.false
+ expect(field.formControl[0].$dirty).to.be.false
+ expect(field.formControl[1].$modelValue).to.be.empty
+ expect(field.formControl[1].$touched).to.be.false
+ expect(field.formControl[1].$dirty).to.be.false
+ })
it(`should work just fine to call resetModel on a field that has no formControl`, () => {
- const field = {template: ' '};
- scope.fields = [field];
- compileAndDigest();
- expect(field.formControl).to.not.exist;
- expect(() => field.resetModel()).to.not.throw();
- });
+ const field = {template: ' '}
+ scope.fields = [field]
+ compileAndDigest()
+ expect(field.formControl).to.not.exist
+ expect(() => field.resetModel()).to.not.throw()
+ })
+
+ it('should reset the form state on the input and form both', () => {
+ const field = getNewField({key: 'foo'})
+ scope.fields = [field]
+ compileAndDigest(`
+
+`)
+ // initial state
+ expect(field.formControl.$dirty).to.be.false
+ expect(field.formControl.$touched).to.be.false
+ expect(scope.theForm.$dirty).to.be.false
+ expect(scope.theForm.$pristine).to.be.true
+ // modification
+ scope.model.foo = '~=[,,_,,]:3'
+ field.formControl.$setTouched()
+ field.formControl.$setDirty()
+ scope.$digest()
+
+ // expect modification
+ expect(field.formControl.$dirty).to.be.true
+ expect(field.formControl.$touched).to.be.true
+ expect(scope.theForm.$dirty).to.be.true
+ expect(scope.theForm.$pristine).to.be.false
+ expect(field.formControl.$modelValue).to.eq('~=[,,_,,]:3')
+
+ // reset state
+ field.resetModel()
+
+ // expect reset
+ expect(field.formControl.$modelValue).to.be.empty
+ expect(field.formControl.$touched).to.be.false
+ expect(field.formControl.$dirty).to.be.false
+ expect(scope.theForm.$dirty).to.be.false
+ expect(scope.theForm.$pristine).to.be.true
+ })
it(`should not digest if there's a digest in progress`, () => {
- scope.fields = [getNewField()];
- compileAndDigest();
- scope.$root.$$phase = '$digest';
- expect(() => field.resetModel()).to.not.throw();
- });
- });
+ scope.fields = [getNewField()]
+ compileAndDigest()
+ scope.$root.$$phase = '$digest'
+ expect(() => field.resetModel()).to.not.throw()
+ })
+ })
describe(`with a div ng-model`, () => {
it(`should have a form-controller`, () => {
- const template = `
`;
- scope.fields = [getNewField({template})];
- compileAndDigest();
- expect(isolateScope.fc).to.exist;
- expect(field.formControl).to.exist;
- });
- });
+ const template = `
`
+ scope.fields = [getNewField({template})]
+ compileAndDigest()
+ expect(isolateScope.fc).to.exist
+ expect(field.formControl).to.exist
+ })
+ })
describe(`with a div data-ng-model`, () => {
it(`should have a form-controller`, () => {
- const template = `
`;
- scope.fields = [getNewField({template})];
- compileAndDigest();
- expect(isolateScope.fc).to.exist;
- expect(field.formControl).to.exist;
- });
- });
-
- describe(`with custom errorExistsAndShouldBeVisible expression`, () => {
- beforeEach(() => {
- scope.fields = [getNewField({validators: {foo: 'false'}})];
- });
-
- it(`should set errorExistsAndShouldBeVisible to true when the expression function says so`, () => {
- formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = '!!options.data.customExpression';
- compileAndDigest();
- expect(field.validation.errorExistsAndShouldBeVisible).to.be.false;
- field.data.customExpression = true;
- scope.$digest();
- expect(field.validation.errorExistsAndShouldBeVisible).to.be.true;
- });
-
- it(`should be able to work with form.$submitted`, () => {
- formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = 'form.$submitted';
- compileAndDigest(`
-
- `);
- expect(field.validation.errorExistsAndShouldBeVisible).to.be.false;
- scope.theForm.$setSubmitted(true);
- scope.$digest();
- expect(field.validation.errorExistsAndShouldBeVisible).to.be.true;
- });
-
- });
+ const template = `
`
+ scope.fields = [getNewField({template})]
+ compileAndDigest()
+ expect(isolateScope.fc).to.exist
+ expect(field.formControl).to.exist
+ })
+ })
+
+ describe(`options.validation.errorExistsAndShouldBeVisible`, () => {
+ describe(`multiple ng-model elements`, () => {
+ beforeEach(() => {
+ scope.fields = [
+ {
+ template: `
+
+
+ `,
+ // we'll just give it a validator that depends on a value we
+ // can change in our tests
+ validators: {foo: '!options.data.invalid'},
+ },
+ ]
+ })
+
+ it(`should set showError to true when one of them is invalid`, () => {
+ compileAndDigest()
+ expect(field.validation.errorExistsAndShouldBeVisible, 'initially false').to.be.false
+ invalidateAndTouchFields()
+
+ expect(field.formControl[0].$error.foo, '$error on the first formControl').be.true
+ expect(field.validation.errorExistsAndShouldBeVisible, 'now true').to.be.true
+ })
+
+ it(`should work with a custom errorExistsAndShouldBeVisibleExpression`, () => {
+ const spy = sinon.spy()
+ formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = spy
+ compileAndDigest()
+
+ invalidateAndTouchFields()
+ expect(spy).to.have.been.calledTwice // once for each form control.
+ })
+
+ function invalidateAndTouchFields() {
+ field.data.invalid = true
+ // force $touched and revalidation of both form controls
+ field.formControl.forEach(fc => {
+ fc.$setTouched()
+ fc.$validate()
+ })
+
+ // redigest to set the showError prop
+ scope.$digest()
+ }
+ })
+
+ describe(`with custom errorExistsAndShouldBeVisible expression`, () => {
+ beforeEach(() => {
+ scope.fields = [getNewField({validators: {foo: 'false'}})]
+ })
+
+ it(`should set errorExistsAndShouldBeVisible to true when the expression function says so`, () => {
+ formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = '!!options.data.customExpression'
+ compileAndDigest()
+ expect(field.validation.errorExistsAndShouldBeVisible).to.be.false
+ field.data.customExpression = true
+ scope.$digest()
+ expect(field.validation.errorExistsAndShouldBeVisible).to.be.true
+ })
+
+ it(`should be able to work with form.$submitted`, () => {
+ formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = 'form.$submitted'
+ compileAndDigest(`
+
+ `)
+ expect(field.validation.errorExistsAndShouldBeVisible).to.be.false
+ scope.theForm.$setSubmitted(true)
+ scope.$digest()
+ expect(field.validation.errorExistsAndShouldBeVisible).to.be.true
+ })
+
+ })
+ })
describe(`with specified "model" property`, () => {
it(`should use the specified model for the field which specifies it`, () => {
const model = {
- foo: 'bar'
- };
+ foo: 'bar',
+ }
scope.fields = [
{template: input, model, key: 'foo'}, // do not use getNewField() here because _.merge creates a copy of model
getNewField(),
- getNewField()
- ];
+ getNewField(),
+ ]
- compileAndDigest();
- expect(isolateScope.model).to.not.equal(scope.model);
- expect(isolateScope.model).to.eq(model);
- });
+ compileAndDigest()
+ expect(isolateScope.model).to.not.equal(scope.model)
+ expect(isolateScope.model).to.eq(model)
+ })
it(`should allow you to specify "formState" and assign it to the formState property`, () => {
scope.fields = [
getNewField({model: 'formState', data: {foo: 'bar'}}),
getNewField(),
- getNewField()
- ];
+ getNewField(),
+ ]
- compileAndDigest();
- const field1 = getIsolateScope(0);
- const field2 = getIsolateScope(1);
- const field3 = getIsolateScope(2);
+ compileAndDigest()
+ const field1 = getIsolateScope(0)
+ const field2 = getIsolateScope(1)
+ const field3 = getIsolateScope(2)
- expect(field1.model).to.not.equal(field2.model);
- expect(field1.model).to.equal(field3.formState);
- expect(field2.model).to.equal(field3.model);
- });
+ expect(field1.model).to.not.equal(field2.model)
+ expect(field1.model).to.equal(field3.formState)
+ expect(field2.model).to.equal(field3.model)
+ })
it(`should allow you to specify any expression which will be used to evaluate the model at compile-time`, () => {
scope.fields = [
- getNewField({key: 'foobar', model: 'options.data.foo', data: {foo: {bar: 'foobar'}}})
- ];
+ getNewField({key: 'foobar', model: 'options.data.foo', data: {foo: {bar: 'foobar'}}}),
+ ]
- compileAndDigest();
- expect(isolateScope.model).to.equal(field.data.foo);
- });
+ compileAndDigest()
+ expect(isolateScope.model).to.equal(field.data.foo)
+ })
it(`should allow you to specify a model for a fieldGroup and have that apply to children fields`, () => {
- scope.model = {child: {foo: 'bar', baz: {boo: {}}}};
+ scope.model = {child: {foo: 'bar', baz: {boo: {}}}}
scope.fields = [
{
model: 'model.child',
fieldGroup: [
getNewField({key: 'foo'}),
- getNewField({key: 'bar', defaultValue: 'foobar', model: 'model.baz.boo'})
- ]
- }
- ];
+ getNewField({key: 'bar', defaultValue: 'foobar', model: 'model.baz.boo'}),
+ ],
+ },
+ ]
- compileAndDigest();
- const fieldGroupNode = node.querySelector('.formly-field-group');
- expect(fieldGroupNode).to.exist;
+ compileAndDigest()
+ const fieldGroupNode = node.querySelector('.formly-field-group')
+ expect(fieldGroupNode).to.exist
- const fieldNode1 = fieldGroupNode.querySelectorAll('.formly-field')[0];
- expect(fieldNode1).to.exist;
+ const fieldNode1 = fieldGroupNode.querySelectorAll('.formly-field')[0]
+ expect(fieldNode1).to.exist
- const fieldNode2 = fieldGroupNode.querySelectorAll('.formly-field')[1];
- expect(fieldNode2).to.exist;
+ const fieldNode2 = fieldGroupNode.querySelectorAll('.formly-field')[1]
+ expect(fieldNode2).to.exist
- const fieldGroup = getIsolateScope(0);
- const field1 = getIsolateScope(1);
- const field2 = getIsolateScope(2);
+ const fieldGroup = getIsolateScope(0)
+ const field1 = getIsolateScope(1)
+ const field2 = getIsolateScope(2)
- expect(fieldGroup.model).to.eq(scope.model.child);
- expect(field1.model).to.eq(scope.model.child);
- expect(field2.model).to.eq(scope.model.child.baz.boo);
- });
+ expect(fieldGroup.model).to.eq(scope.model.child)
+ expect(field1.model).to.eq(scope.model.child)
+ expect(field2.model).to.eq(scope.model.child.baz.boo)
+ })
it(`should throw an error if the model does not exist`, () => {
- scope.model = {child: {}};
+ scope.model = {child: {}}
scope.fields = [
{
model: 'model.child',
fieldGroup: [
- getNewField({model: 'model.baz'})
- ]
- }
- ];
+ getNewField({model: 'model.baz'}),
+ ],
+ },
+ ]
- expect(() => compileAndDigest()).to.throw();
- });
+ expect(() => compileAndDigest()).to.throw()
+ })
it(`should watch the model when it's not the direct child of a formly-form`, () => {
scope.fields = [
- getNewField({key: 'foo', model: {}})
- ];
+ getNewField({key: 'foo', model: {}}),
+ ]
+
+ compileAndDigest('
')
+ $timeout.flush()
+
+ const expressionPropertySpy = sinon.spy()
+ field.expressionProperties = {'data.dummy': expressionPropertySpy}
- compileAndDigest('
');
- $timeout.flush();
+ field.model.foo = 'hello'
+ scope.$digest()
+ $timeout.flush()
- const expressionPropertySpy = sinon.spy();
- field.expressionProperties = {'data.dummy': expressionPropertySpy};
+ expect(expressionPropertySpy).to.have.been.calledOnce
+ })
- field.model.foo = 'hello';
- scope.$digest();
- $timeout.flush();
+ it(`should add watches on deep dive fields`, () => {
+ const formWithOptions = ' '
+ scope.model = {}
+ scope.options = {}
- expect(expressionPropertySpy).to.have.been.calledOnce;
- });
+ const deepLinkField = getNewField()
+ deepLinkField.key = 'foo.bar'
+ deepLinkField.watcher = {
+ listener: sinon.spy(),
+ }
+
+ scope.fields = [deepLinkField]
+ compileAndDigest(formWithOptions)
+ expect(deepLinkField.watcher.listener).to.have.been.called
+ scope.model.foo = {
+ bar: 'brown',
+ }
+ scope.$digest()
+ expect(deepLinkField.watcher.listener).to.have.been.called
+ })
it('should make original model available on field scope, even another model has been set for field', () => {
- scope.model = {foo: 'bar', child: {fox: 'jumps'}};
+ scope.model = {foo: 'bar', child: {fox: 'jumps'}}
scope.fields = [
getNewField({key: 'foo '}),
- getNewField({key: 'bar', model: 'model.child'})
- ];
+ getNewField({key: 'bar', model: 'model.child'}),
+ ]
- compileAndDigest();
+ compileAndDigest()
- const field1 = getIsolateScope(0);
- const field2 = getIsolateScope(1);
+ const field1 = getIsolateScope(0)
+ const field2 = getIsolateScope(1)
- expect(field1.model).to.eq(scope.model);
- expect(field1.originalModel).to.eq(field1.model);
+ expect(field1.model).to.eq(scope.model)
+ expect(field1.originalModel).to.eq(field1.model)
- expect(field2.model).to.eq(scope.model.child);
- expect(field2.originalModel).not.to.eq(scope.model.child);
- expect(field2.originalModel).to.eq(scope.model);
- });
+ expect(field2.model).to.eq(scope.model.child)
+ expect(field2.originalModel).not.to.eq(scope.model.child)
+ expect(field2.originalModel).to.eq(scope.model)
+ })
it('should take field model as default for original model, if original value attributes has not been set', () => {
scope.fields = [
- getNewField({key: 'foo', model: {foo: 'bar'}})
- ];
+ getNewField({key: 'foo', model: {foo: 'bar'}}),
+ ]
- compileAndDigest('
');
- $timeout.flush();
+ compileAndDigest('
')
+ $timeout.flush()
- expect(field.model).to.eq(scope.fields[0].model);
- expect(field.originalModel).to.eql(scope.fields[0].model);
+ expect(field.model).to.eq(scope.fields[0].model)
+ expect(field.originalModel).to.eql(scope.fields[0].model)
- });
+ })
- });
+ })
describe(`fieldGroup`, () => {
it(`should share the form with a fieldGroup`, () => {
- scope.model = {child: {foo: 'bar'}};
+ scope.model = {child: {foo: 'bar'}}
scope.fields = [
{
model: 'model.child',
fieldGroup: [
- getNewField({key: 'foo'})
- ]
- }
- ];
+ getNewField({key: 'foo'}),
+ ],
+ },
+ ]
- compileAndDigest();
- const fieldGroupNode = node.querySelector('.formly-field-group');
- expect(fieldGroupNode).to.exist;
+ compileAndDigest()
+ const fieldGroupNode = node.querySelector('.formly-field-group')
+ expect(fieldGroupNode).to.exist
- const fieldGroup = getIsolateScope(0);
+ const fieldGroup = getIsolateScope(0)
- expect(fieldGroup.model).to.eq(scope.model.child);
+ expect(fieldGroup.model).to.eq(scope.model.child)
- expect(fieldGroup.options.form).to.eq(fieldGroup.form);
- });
+ expect(fieldGroup.options.form).to.eq(fieldGroup.form)
+ })
it(`should allow you to specify a key which will be used for the model of the field-group`, () => {
scope.fields = [
@@ -1524,104 +1798,104 @@ describe('formly-field', function() {
{
key: 'bar',
fieldGroup: [
- getNewField({key: 'barChild', defaultValue: 'barVal'})
- ]
- }
- ]
- }
- ];
- compileAndDigest();
+ getNewField({key: 'barChild', defaultValue: 'barVal'}),
+ ],
+ },
+ ],
+ },
+ ]
+ compileAndDigest()
- expect(scope.model.foo.fooChild).to.eq('fooVal');
- expect(scope.model.foo.bar.barChild).to.eq('barVal');
- });
- });
+ expect(scope.model.foo.fooChild).to.eq('fooVal')
+ expect(scope.model.foo.bar.barChild).to.eq('barVal')
+ })
+ })
describe(`runExpressions`, () => {
describe(`as functions`, () => {
it(`should invoke the expressionProperties with the $viewValue, $modelValue, and scope`, () => {
- const spy = sinon.spy();
+ const spy = sinon.spy()
scope.model = {
- foo: 'bar'
- };
+ foo: 'bar',
+ }
scope.fields = [
getNewField({
key: 'foo',
expressionProperties: {
- 'templateOptions.disabled': spy
- }
- })
- ];
- compileAndDigest();
- $timeout.flush(); // <-- runExpressions happens inside a $timeout
- expect(spy).to.have.been.calledWith('bar', 'bar', isolateScope);
- });
- });
- });
+ 'templateOptions.disabled': spy,
+ },
+ }),
+ ]
+ compileAndDigest()
+ $timeout.flush() // <-- runExpressions happens inside a $timeout
+ expect(spy).to.have.been.calledWith('bar', 'bar', isolateScope)
+ })
+ })
+ })
describe(`templateManipulators and wrappers`, () => {
it(`should not cause a problem when you don't pass form-options`, () => {
- const fieldScope = scope.$new();
- fieldScope.field = {template: 'foo', model: {}};
- fieldScope.fields = [fieldScope.field];
+ const fieldScope = scope.$new()
+ fieldScope.field = {template: 'foo', model: {}}
+ fieldScope.fields = [fieldScope.field]
expect(() => {
compileAndDigest(`
`,
- fieldScope);
- }).to.not.throw();
- });
+ fieldScope)
+ }).to.not.throw()
+ })
it(`should allow you to specify a templateManipulator on a field and form basis and they should be applied in the correct order`, () => {
formlyConfig.setWrapper({
name: 'formWrapper1',
- template: '__formWrapper1__ '
- });
+ template: '__formWrapper1__ ',
+ })
formlyConfig.setWrapper({
name: 'formWrapper2',
- template: '__formWrapper2__ '
- });
+ template: '__formWrapper2__ ',
+ })
formlyConfig.setWrapper({
name: 'fieldWrapper1',
- template: '__fieldWrapper1__ '
- });
+ template: '__fieldWrapper1__ ',
+ })
formlyConfig.setWrapper({
name: 'fieldWrapper2',
- template: '__fieldWrapper2__ '
- });
+ template: '__fieldWrapper2__ ',
+ })
- const fieldPre1 = sinon.spy(template => `fieldPre1_${template}`);
- const fieldPre2 = sinon.spy(template => `fieldPre2_${template}`);
- const fieldPost1 = sinon.spy(template => `fieldPost1_${template}`);
- const fieldPost2 = sinon.spy(template => `fieldPost2_${template}`);
+ const fieldPre1 = sinon.spy(template => `fieldPre1_${template}`)
+ const fieldPre2 = sinon.spy(template => `fieldPre2_${template}`)
+ const fieldPost1 = sinon.spy(template => `fieldPost1_${template}`)
+ const fieldPost2 = sinon.spy(template => `fieldPost2_${template}`)
- const formPre1 = sinon.spy(template => `formPre1_${template}`);
- const formPre2 = sinon.spy(template => `formPre2_${template}`);
- const formPost1 = sinon.spy(template => `formPost1_${template}`);
- const formPost2 = sinon.spy(template => `formPost2_${template}`);
+ const formPre1 = sinon.spy(template => `formPre1_${template}`)
+ const formPre2 = sinon.spy(template => `formPre2_${template}`)
+ const formPost1 = sinon.spy(template => `formPost1_${template}`)
+ const formPost2 = sinon.spy(template => `formPost2_${template}`)
- const globalPre1 = sinon.spy(template => `globalPre1_${template}`);
- const globalPre2 = sinon.spy(template => `globalPre2_${template}`);
- const globalPost1 = sinon.spy(template => `globalPost1_${template}`);
- const globalPost2 = sinon.spy(template => `globalPost2_${template}`);
+ const globalPre1 = sinon.spy(template => `globalPre1_${template}`)
+ const globalPre2 = sinon.spy(template => `globalPre2_${template}`)
+ const globalPost1 = sinon.spy(template => `globalPost1_${template}`)
+ const globalPost2 = sinon.spy(template => `globalPost2_${template}`)
- formlyConfig.templateManipulators.preWrapper.push(globalPre1);
- formlyConfig.templateManipulators.preWrapper.push(globalPre2);
- formlyConfig.templateManipulators.postWrapper.push(globalPost1);
- formlyConfig.templateManipulators.postWrapper.push(globalPost2);
+ formlyConfig.templateManipulators.preWrapper.push(globalPre1)
+ formlyConfig.templateManipulators.preWrapper.push(globalPre2)
+ formlyConfig.templateManipulators.postWrapper.push(globalPost1)
+ formlyConfig.templateManipulators.postWrapper.push(globalPost2)
scope.options = {
templateManipulators: {
preWrapper: [formPre1, formPre2],
- postWrapper: [formPost1, formPost2]
+ postWrapper: [formPost1, formPost2],
},
- wrapper: ['formWrapper1', 'formWrapper2']
- };
+ wrapper: ['formWrapper1', 'formWrapper2'],
+ }
scope.fields = [
getNewField({
@@ -1629,106 +1903,156 @@ describe('formly-field', function() {
wrapper: ['fieldWrapper1', 'fieldWrapper2'],
templateManipulators: {
preWrapper: [fieldPre1, fieldPre2],
- postWrapper: [fieldPost1, fieldPost2]
- }
- })
- ];
+ postWrapper: [fieldPost1, fieldPost2],
+ },
+ }),
+ ]
- compileAndDigest();
+ compileAndDigest()
// field pre
- expect(fieldPre1).to.have.been.calledWith('foo', field, isolateScope);
- expect(fieldPre1).to.have.returned('fieldPre1_foo');
+ expect(fieldPre1).to.have.been.calledWith('foo', field, isolateScope)
+ expect(fieldPre1).to.have.returned('fieldPre1_foo')
- expect(fieldPre2).to.have.been.calledWith('fieldPre1_foo', field, isolateScope);
- expect(fieldPre2).to.have.returned('fieldPre2_fieldPre1_foo');
+ expect(fieldPre2).to.have.been.calledWith('fieldPre1_foo', field, isolateScope)
+ expect(fieldPre2).to.have.returned('fieldPre2_fieldPre1_foo')
// form pre
- expect(formPre1).to.have.been.calledWith('fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(formPre1).to.have.returned('formPre1_fieldPre2_fieldPre1_foo');
+ expect(formPre1).to.have.been.calledWith('fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(formPre1).to.have.returned('formPre1_fieldPre2_fieldPre1_foo')
- expect(formPre2).to.have.been.calledWith('formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(formPre2).to.have.returned('formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(formPre2).to.have.been.calledWith('formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(formPre2).to.have.returned('formPre2_formPre1_fieldPre2_fieldPre1_foo')
// global pre
- expect(globalPre1).to.have.been.calledWith('formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(globalPre1).to.have.returned('globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(globalPre1).to.have.been.calledWith('formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(globalPre1).to.have.returned('globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
- expect(globalPre2).to.have.been.calledWith('globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(globalPre2).to.have.returned('globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(globalPre2).to.have.been.calledWith('globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(globalPre2).to.have.returned('globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
// this is where the wrapper runs
// field post
- expect(fieldPost1).to.have.been.calledWith('__formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(fieldPost1).to.have.returned('fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(fieldPost1).to.have.been.calledWith('__formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(fieldPost1).to.have.returned('fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
- expect(fieldPost2).to.have.been.calledWith('fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(fieldPost2).to.have.returned('fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(fieldPost2).to.have.been.calledWith('fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(fieldPost2).to.have.returned('fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
// form post
- expect(formPost1).to.have.been.calledWith('fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(formPost1).to.have.returned('formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(formPost1).to.have.been.calledWith('fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(formPost1).to.have.returned('formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
- expect(formPost2).to.have.been.calledWith('formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(formPost2).to.have.returned('formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(formPost2).to.have.been.calledWith('formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(formPost2).to.have.returned('formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
// global post
- expect(globalPost1).to.have.been.calledWith('formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(globalPost1).to.have.returned('globalPost1_formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
+ expect(globalPost1).to.have.been.calledWith('formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(globalPost1).to.have.returned('globalPost1_formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
- expect(globalPost2).to.have.been.calledWith('globalPost1_formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope);
- expect(globalPost2).to.have.returned('globalPost2_globalPost1_formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo');
- });
- });
+ expect(globalPost2).to.have.been.calledWith('globalPost1_formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo', field, isolateScope)
+ expect(globalPost2).to.have.returned('globalPost2_globalPost1_formPost2_formPost1_fieldPost2_fieldPost1___formWrapper2____formWrapper1____fieldWrapper2____fieldWrapper1__globalPre2_globalPre1_formPre2_formPre1_fieldPre2_fieldPre1_foo')
+ })
+ })
describe(`extras`, () => {
describe(`validateOnModelChange`, () => {
it(`should invoke $validate on the field even when the field's model hasn't changed`, () => {
- scope.fields = [getNewField({extras: {validateOnModelChange: true}})];
- compileAndDigest();
- const $validateSpy = sinon.spy(field.formControl, '$validate');
- scope.model.foo = 'bar';
- scope.$digest();
- $timeout.flush();
- expect($validateSpy).to.have.been.calledOnce;
- });
- });
- });
+ scope.fields = [getNewField({extras: {validateOnModelChange: true}})]
+ compileAndDigest()
+ const $validateSpy = sinon.spy(field.formControl, '$validate')
+ scope.model.foo = 'bar'
+ scope.$digest()
+ $timeout.flush()
+ expect($validateSpy).to.have.been.calledOnce
+ })
+
+ it(`should cope when field.formControl has been upgraded to an array`, () => {
+ scope.model = {
+ multiNgModel: {
+ start: 'start',
+ stop: 'stop',
+ },
+ }
+ const field = getNewField({
+ key: 'multiNgModel',
+ template: multiNgModelField,
+ extras: {
+ validateOnModelChange: true,
+ },
+ })
+ scope.fields = [field]
+ compileAndDigest()
+ const $validateSpy0 = sinon.spy(field.formControl[0], '$validate')
+ const $validateSpy1 = sinon.spy(field.formControl[1], '$validate')
+ scope.model.foo = 'bar'
+ scope.$digest()
+ $timeout.flush()
+ expect($validateSpy0).to.have.been.calledOnce
+ expect($validateSpy1).to.have.been.calledOnce
+ })
+
+ it.skip(`should run field expressions when form is initialised`, () => {
+ scope.model = {email: ''}
+ scope.fields = [getNewField({
+ key: 'email',
+ templateOptions: {
+ required: true,
+ },
+ extras: {validateOnModelChange: true},
+ }),
+ getNewField({
+ key: 'firstName',
+ templateOptions: {
+ required: true,
+ },
+ extras: {validateOnModelChange: true},
+ hideExpression: 'form.email.$invalid',
+ })]
+
+ compileAndDigest()
+ $timeout.flush()
+ scope.$digest()
+ expect(scope.fields[1].formControl).to.exist
+ expect(scope.fields[1].hide).to.equal(true)
+ })
+ })
+ })
describe(`other things`, () => {
it(`should warn if you specify 'hide' in expressionProperties`, inject(($log) => {
- scope.fields = [getNewField({expressionProperties: {hide: 'foo'}})];
- compileAndDigest();
- const log = $log.warn.logs[0];
- expect($log.warn.logs).to.have.length(1);
- expect(log[0]).to.equal('Formly Warning:');
+ scope.fields = [getNewField({expressionProperties: {hide: 'foo'}})]
+ compileAndDigest()
+ const log = $log.warn.logs[0]
+ expect($log.warn.logs).to.have.length(1)
+ expect(log[0]).to.equal('Formly Warning:')
expect(log[1]).to.equal(
'You have specified `hide` in `expressionProperties`. Use `hideExpression` instead'
- );
- expect(log[2]).to.equal(field);
- }));
+ )
+ expect(log[2]).to.equal(field)
+ }))
it(`should add a bunch of things to the formly field and it's scope`, () => {
- scope.fields = [{template: ' '}];
- compileAndDigest();
+ scope.fields = [{template: ' '}]
+ compileAndDigest()
// here's a list of everything that angular-formly adds for you.
expect(field).to.contain.all.keys([
'key', 'extras', 'data', 'templateOptions', 'validation', 'value', 'runExpressions',
- 'resetModel', 'updateInitialValue', 'id', 'name', 'initialValue', 'formControl'
- ]);
- });
+ 'resetModel', 'updateInitialValue', 'id', 'name', 'initialValue', 'formControl',
+ ])
+ })
it(`should add a bunch of things to the formly field and it's scope`, () => {
- scope.fields = [{template: ' '}];
- compileAndDigest();
+ scope.fields = [{template: ' '}]
+ compileAndDigest()
// here's a list of everything that you have available on the scope for your templates
expect(isolateScope).to.contain.all.keys([
'options', 'model', 'formId', 'index', 'fields', 'formState', 'formOptions',
- 'form', 'id', 'to', 'fc', 'name', 'showError'
- ]);
- });
- });
+ 'form', 'id', 'to', 'fc', 'name', 'showError',
+ ])
+ })
+ })
describe(`merging of options`, () => {
describe(`extends`, () => {
@@ -1736,42 +2060,42 @@ describe('formly-field', function() {
formlyConfig.setType({
name: 'hr',
template: ' ',
- defaultOptions: () => ({data: {foo: 'bar', baz: 'foobar'}})
- });
- });
+ defaultOptions: () => ({data: {foo: 'bar', baz: 'foobar'}}),
+ })
+ })
it(`should merge the options properly`, () => {
- const field = {type: 'hr', data: {baz: 'barfoo'}};
- scope.fields = [field];
- compileAndDigest();
- expect(field.data.baz).to.equal('barfoo');
- expect(field.data.foo).to.equal('bar');
- });
- });
- });
+ const field = {type: 'hr', data: {baz: 'barfoo'}}
+ scope.fields = [field]
+ compileAndDigest()
+ expect(field.data.baz).to.equal('barfoo')
+ expect(field.data.foo).to.equal('bar')
+ })
+ })
+ })
function compileAndDigest(template = basicForm, context = scope) {
- el = $compile(template)(context);
- context.$digest();
- node = el[0];
- field = context.fields[0];
- isolateScope = getIsolateScope();
- return el;
+ el = $compile(template)(context)
+ context.$digest()
+ node = el[0]
+ field = context.fields[0]
+ isolateScope = getIsolateScope()
+ return el
}
function getIsolateScope(index = 0) {
- return angular.element(getFieldNode(index)).isolateScope();
+ return angular.element(getFieldNode(index)).isolateScope()
}
function getFieldNode(index = 0) {
- return node.querySelectorAll('.formly-field')[index];
+ return node.querySelectorAll('.formly-field')[index]
}
function getFieldNgModelNode(index = 0) {
- return getFieldNode(index).querySelector('[ng-model]');
+ return getFieldNode(index).querySelector('[ng-model]')
}
function getNgModelCtrl(index = 0) {
- return angular.element(getFieldNgModelNode(index)).controller('ngModel');
+ return angular.element(getFieldNgModelNode(index)).controller('ngModel')
}
-});
+})
diff --git a/src/directives/formly-focus.js b/src/directives/formly-focus.js
index 5ed9d774..d39bdd25 100644
--- a/src/directives/formly-focus.js
+++ b/src/directives/formly-focus.js
@@ -1,29 +1,29 @@
-export default formlyFocus;
+export default formlyFocus
// @ngInject
function formlyFocus($timeout, $document) {
return {
restrict: 'A',
link: function formlyFocusLink(scope, element, attrs) {
- let previousEl = null;
- const el = element[0];
- const doc = $document[0];
+ let previousEl = null
+ const el = element[0]
+ const doc = $document[0]
attrs.$observe('formlyFocus', function respondToFocusExpressionChange(value) {
/* eslint no-bitwise:0 */ // I know what I'm doing. I promise...
if (value === 'true') {
$timeout(function setElementFocus() {
- previousEl = doc.activeElement;
- el.focus();
- }, ~~attrs.focusWait);
+ previousEl = doc.activeElement
+ el.focus()
+ }, ~~attrs.focusWait)
} else if (value === 'false') {
if (doc.activeElement === el) {
- el.blur();
+ el.blur()
if (attrs.hasOwnProperty('refocus') && previousEl) {
- previousEl.focus();
+ previousEl.focus()
}
}
}
- });
- }
- };
+ })
+ },
+ }
}
diff --git a/src/directives/formly-focus.test.js b/src/directives/formly-focus.test.js
index 3c4e25ba..c30a11fa 100644
--- a/src/directives/formly-focus.test.js
+++ b/src/directives/formly-focus.test.js
@@ -1,99 +1,99 @@
/* eslint max-len:[2,200] */
-import _ from 'lodash';
+import _ from 'lodash'
describe('formly-form', () => {
- let $compile, scope, el, node, input, textarea, $timeout;
+ let $compile, scope, el, node, input, textarea, $timeout
- const basicTemplate = '
';
- beforeEach(window.module('formly'));
+ const basicTemplate = '
'
+ beforeEach(window.module('formly'))
beforeEach(inject((_$compile_, _$timeout_, $rootScope) => {
- $compile = _$compile_;
- $timeout = _$timeout_;
- scope = $rootScope.$new();
- }));
+ $compile = _$compile_
+ $timeout = _$timeout_
+ scope = $rootScope.$new()
+ }))
it(`focus the element when focus is set to "true" and then blurred when it's set to "false"`, () => {
- compileAndDigest();
- expectFocus(false);
- scope.focus = true;
- scope.$digest();
- $timeout.flush();
- expectFocus(true);
-
- scope.focus = false;
- scope.$digest();
- expectFocus(false);
- });
+ compileAndDigest()
+ expectFocus(false)
+ scope.focus = true
+ scope.$digest()
+ $timeout.flush()
+ expectFocus(true)
+
+ scope.focus = false
+ scope.$digest()
+ expectFocus(false)
+ })
it(`should not bother unfocusing the element if it doesn't have focus to begin with`, () => {
- compileAndDigest();
- expectFocus(false);
- scope.focus = false;
- scope.$digest();
- expectFocus(false);
- });
+ compileAndDigest()
+ expectFocus(false)
+ scope.focus = false
+ scope.$digest()
+ expectFocus(false)
+ })
it(`should refocus the previously focused element`, () => {
- compileAndDigest();
- textarea.focus();
- scope.focus = true;
- scope.$digest();
- $timeout.flush();
- expectFocus(true);
-
- scope.focus = false;
- scope.$digest();
- expectFocus(false);
- expectFocus(true, textarea);
- });
+ compileAndDigest()
+ textarea.focus()
+ scope.focus = true
+ scope.$digest()
+ $timeout.flush()
+ expectFocus(true)
+
+ scope.focus = false
+ scope.$digest()
+ expectFocus(false)
+ expectFocus(true, textarea)
+ })
it(`should not refocus the previously focused element when the refocus attribute doesn't exist`, () => {
compileAndDigest(
'
'
- );
- textarea.focus();
- scope.focus = true;
- scope.$digest();
- $timeout.flush();
- expectFocus(true);
-
- scope.focus = false;
- scope.$digest();
- expectFocus(false);
- expectFocus(false, textarea);
- });
+ )
+ textarea.focus()
+ scope.focus = true
+ scope.$digest()
+ $timeout.flush()
+ expectFocus(true)
+
+ scope.focus = false
+ scope.$digest()
+ expectFocus(false)
+ expectFocus(false, textarea)
+ })
it(`should not refocus if the previously active element has not been set`, () => {
- compileAndDigest(undefined, {focus: false});
- expectFocus(false);
- });
+ compileAndDigest(undefined, {focus: false})
+ expectFocus(false)
+ })
afterEach(() => {
if (document.body.contains(node)) {
- node.parentNode.removeChild(node);
+ node.parentNode.removeChild(node)
}
- });
+ })
function expectFocus(focus, focusedNode = input) {
- const isFocused = document.activeElement === focusedNode;
+ const isFocused = document.activeElement === focusedNode
if (focus) {
- expect(isFocused, 'expected focused element to be: ' + focusedNode + ' but it was ' + document.activeElement).to.be.true;
+ expect(isFocused, 'expected focused element to be: ' + focusedNode + ' but it was ' + document.activeElement).to.be.true
} else {
- expect(isFocused, 'expected focused element to not be: ' + focusedNode + ' but it was ' + document.activeElement).to.be.false;
+ expect(isFocused, 'expected focused element to not be: ' + focusedNode + ' but it was ' + document.activeElement).to.be.false
}
}
function compileAndDigest(template = basicTemplate, scopeOverrides = {}) {
- _.assign(scope, scopeOverrides);
- el = $compile(template)(scope);
- scope.$digest();
- node = el[0];
- document.body.appendChild(node);
- input = document.getElementById('main-input');
- textarea = document.getElementById('textarea');
- return el;
+ _.assign(scope, scopeOverrides)
+ el = $compile(template)(scope)
+ scope.$digest()
+ node = el[0]
+ document.body.appendChild(node)
+ input = document.getElementById('main-input')
+ textarea = document.getElementById('textarea')
+ return el
}
-});
+})
diff --git a/src/directives/formly-form.controller.js b/src/directives/formly-form.controller.js
new file mode 100644
index 00000000..94fa4fe0
--- /dev/null
+++ b/src/directives/formly-form.controller.js
@@ -0,0 +1,301 @@
+import angular from 'angular-fix'
+
+function isFieldGroup(field) {
+ return field && !!field.fieldGroup
+}
+
+// @ngInject
+export default function FormlyFormController(
+ formlyUsability, formlyWarn, formlyConfig, $parse, $scope, formlyApiCheck, formlyUtil) {
+
+ setupOptions()
+ $scope.model = $scope.model || {}
+ setupFields()
+
+ // watch the model and evaluate watch expressions that depend on it.
+ if (!$scope.options.manualModelWatcher) {
+ $scope.$watch('model', onModelOrFormStateChange, true)
+ } else if (angular.isFunction($scope.options.manualModelWatcher)) {
+ $scope.$watch($scope.options.manualModelWatcher, onModelOrFormStateChange, true)
+ }
+
+ if ($scope.options.formState) {
+ $scope.$watch('options.formState', onModelOrFormStateChange, true)
+ }
+
+ function onModelOrFormStateChange() {
+ angular.forEach($scope.fields, runFieldExpressionProperties)
+ }
+
+ function validateFormControl(formControl, promise) {
+ const validate = formControl.$validate
+ if (promise) {
+ promise.then(() => validate.apply(formControl))
+ } else {
+ validate()
+ }
+ }
+
+ function runFieldExpressionProperties(field, index) {
+ const model = field.model || $scope.model
+ const promise = field.runExpressions && field.runExpressions()
+ if (field.hideExpression) { // can't use hide with expressionProperties reliably
+ const val = model[field.key]
+ field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index, {model})
+ }
+ if (field.extras && field.extras.validateOnModelChange && field.formControl) {
+ if (angular.isArray(field.formControl)) {
+ angular.forEach(field.formControl, function(formControl) {
+ validateFormControl(formControl, promise)
+ })
+ } else {
+ validateFormControl(field.formControl, promise)
+ }
+ }
+ }
+
+ function setupFields() {
+ $scope.fields = $scope.fields || []
+
+ checkDeprecatedOptions($scope.options)
+
+ let fieldTransforms = $scope.options.fieldTransform || formlyConfig.extras.fieldTransform
+
+ if (!angular.isArray(fieldTransforms)) {
+ fieldTransforms = [fieldTransforms]
+ }
+
+ angular.forEach(fieldTransforms, function transformFields(fieldTransform) {
+ if (fieldTransform) {
+ $scope.fields = fieldTransform($scope.fields, $scope.model, $scope.options, $scope.form)
+ if (!$scope.fields) {
+ throw formlyUsability.getFormlyError('fieldTransform must return an array of fields')
+ }
+ }
+ })
+
+ setupModels()
+
+ if ($scope.options.watchAllExpressions) {
+ angular.forEach($scope.fields, setupHideExpressionWatcher)
+ }
+
+ angular.forEach($scope.fields, attachKey) // attaches a key based on the index if a key isn't specified
+ angular.forEach($scope.fields, setupWatchers) // setup watchers for all fields
+ }
+
+ function checkDeprecatedOptions(options) {
+ if (formlyConfig.extras.fieldTransform && angular.isFunction(formlyConfig.extras.fieldTransform)) {
+ formlyWarn(
+ 'fieldtransform-as-a-function-deprecated',
+ 'fieldTransform as a function has been deprecated.',
+ `Attempted for formlyConfig.extras: ${formlyConfig.extras.fieldTransform.name}`,
+ formlyConfig.extras
+ )
+ } else if (options.fieldTransform && angular.isFunction(options.fieldTransform)) {
+ formlyWarn(
+ 'fieldtransform-as-a-function-deprecated',
+ 'fieldTransform as a function has been deprecated.',
+ `Attempted for form`,
+ options
+ )
+ }
+ }
+
+ function setupOptions() {
+ formlyApiCheck.throw(
+ [formlyApiCheck.formOptionsApi.optional], [$scope.options], {prefix: 'formly-form options check'}
+ )
+ $scope.options = $scope.options || {}
+ $scope.options.formState = $scope.options.formState || {}
+
+ angular.extend($scope.options, {
+ updateInitialValue,
+ resetModel,
+ })
+
+ }
+
+ function updateInitialValue() {
+ angular.forEach($scope.fields, field => {
+ if (isFieldGroup(field) && field.options) {
+ field.options.updateInitialValue()
+ } else {
+ field.updateInitialValue()
+ }
+ })
+ }
+
+ function resetModel() {
+ angular.forEach($scope.fields, field => {
+ if (isFieldGroup(field) && field.options) {
+ field.options.resetModel()
+ } else if (field.resetModel) {
+ field.resetModel()
+ }
+ })
+ }
+
+ function setupModels() {
+ // a set of field models that are already watched (the $scope.model will have its own watcher)
+ const watchedModels = [$scope.model]
+ // we will not set up automatic model watchers if manual mode is set
+ const manualModelWatcher = $scope.options.manualModelWatcher
+
+ if ($scope.options.formState) {
+ // $scope.options.formState will have its own watcher
+ watchedModels.push($scope.options.formState)
+ }
+
+ angular.forEach($scope.fields, (field) => {
+ const isNewModel = initModel(field)
+
+ if (field.model && isNewModel && watchedModels.indexOf(field.model) === -1 && !manualModelWatcher) {
+ $scope.$watch(() => field.model, onModelOrFormStateChange, true)
+ watchedModels.push(field.model)
+ }
+ })
+ }
+
+ function setupHideExpressionWatcher(field, index) {
+ if (field.hideExpression) { // can't use hide with expressionProperties reliably
+ const model = field.model || $scope.model
+ $scope.$watch(function hideExpressionWatcher() {
+ const val = model[field.key]
+ return evalCloseToFormlyExpression(field.hideExpression, val, field, index, {model})
+ }, (hide) => field.hide = hide, true)
+ }
+ }
+
+ function initModel(field) {
+ let isNewModel = true
+
+ if (angular.isString(field.model)) {
+ const expression = field.model
+
+ isNewModel = !referencesCurrentlyWatchedModel(expression)
+
+ field.model = resolveStringModel(expression)
+
+ $scope.$watch(() => resolveStringModel(expression), (model) => field.model = model)
+ }
+
+ return isNewModel
+
+ function resolveStringModel(expression) {
+ const index = $scope.fields.indexOf(field)
+ const model = evalCloseToFormlyExpression(expression, undefined, field, index, {model: $scope.model})
+
+ if (!model) {
+ throw formlyUsability.getFieldError(
+ 'field-model-must-be-initialized',
+ 'Field model must be initialized. When specifying a model as a string for a field, the result of the' +
+ ' expression must have been initialized ahead of time.',
+ field)
+ }
+
+ return model
+ }
+ }
+
+ function referencesCurrentlyWatchedModel(expression) {
+ return ['model', 'formState'].some(item => {
+ return formlyUtil.startsWith(expression, `${item}.`) || formlyUtil.startsWith(expression, `${item}[`)
+ })
+ }
+
+ function attachKey(field, index) {
+ if (!isFieldGroup(field)) {
+ field.key = field.key || index || 0
+ }
+ }
+
+ function setupWatchers(field, index) {
+ if (!angular.isDefined(field.watcher)) {
+ return
+ }
+ let watchers = field.watcher
+ if (!angular.isArray(watchers)) {
+ watchers = [watchers]
+ }
+ angular.forEach(watchers, function setupWatcher(watcher) {
+ if (!angular.isDefined(watcher.listener) && !watcher.runFieldExpressions) {
+ throw formlyUsability.getFieldError(
+ 'all-field-watchers-must-have-a-listener',
+ 'All field watchers must have a listener', field
+ )
+ }
+ const watchExpression = getWatchExpression(watcher, field, index)
+ const watchListener = getWatchListener(watcher, field, index)
+
+ const type = watcher.type || '$watch'
+ watcher.stopWatching = $scope[type](watchExpression, watchListener, watcher.watchDeep)
+ })
+ }
+
+ function getWatchExpression(watcher, field, index) {
+ let watchExpression
+ if (!angular.isUndefined(watcher.expression)) {
+ watchExpression = watcher.expression
+ } else if (field.key) {
+ watchExpression = 'model[\'' + field.key.toString().split('.').join('\'][\'') + '\']'
+ }
+ if (angular.isFunction(watchExpression)) {
+ // wrap the field's watch expression so we can call it with the field as the first arg
+ // and the stop function as the last arg as a helper
+ const originalExpression = watchExpression
+ watchExpression = function formlyWatchExpression() {
+ const args = modifyArgs(watcher, index, ...arguments)
+ return originalExpression(...args)
+ }
+ watchExpression.displayName = `Formly Watch Expression for field for ${field.key}`
+ } else if (field.model) {
+ watchExpression = $parse(watchExpression).bind(null, $scope, {model: field.model})
+ }
+ return watchExpression
+ }
+
+ function getWatchListener(watcher, field, index) {
+ let watchListener = watcher.listener
+ if (angular.isFunction(watchListener) || watcher.runFieldExpressions) {
+ // wrap the field's watch listener so we can call it with the field as the first arg
+ // and the stop function as the last arg as a helper
+ const originalListener = watchListener
+ watchListener = function formlyWatchListener() {
+ let value
+ if (originalListener) {
+ const args = modifyArgs(watcher, index, ...arguments)
+ value = originalListener(...args)
+ }
+ if (watcher.runFieldExpressions) {
+ runFieldExpressionProperties(field, index)
+ }
+ return value
+ }
+ watchListener.displayName = `Formly Watch Listener for field for ${field.key}`
+ }
+ return watchListener
+ }
+
+ function modifyArgs(watcher, index, ...originalArgs) {
+ return [$scope.fields[index], ...originalArgs, watcher.stopWatching]
+ }
+
+ function evalCloseToFormlyExpression(expression, val, field, index, extraLocals = {}) {
+ extraLocals = angular.extend(getFormlyFieldLikeLocals(field, index), extraLocals)
+ return formlyUtil.formlyEval($scope, expression, val, val, extraLocals)
+ }
+
+ function getFormlyFieldLikeLocals(field, index) {
+ // this makes it closer to what a regular formlyExpression would be
+ return {
+ model: field.model,
+ options: field,
+ index,
+ formState: $scope.options.formState,
+ originalModel: $scope.model,
+ formOptions: $scope.options,
+ formId: $scope.formId,
+ }
+ }
+}
diff --git a/src/directives/formly-form.controller.test.js b/src/directives/formly-form.controller.test.js
new file mode 100644
index 00000000..eb01be9f
--- /dev/null
+++ b/src/directives/formly-form.controller.test.js
@@ -0,0 +1,122 @@
+/* eslint no-shadow:0 */
+/* eslint no-console:0 */
+/* eslint max-len:0 */
+/* eslint max-nested-callbacks:0 */
+import {getNewField, shouldWarnWithLog} from '../test.utils.js'
+import _ from 'lodash'
+
+describe('FormlyFormController', () => {
+ let $controller, formlyConfig, scope
+ beforeEach(window.module('formly'))
+ beforeEach(inject((_formlyConfig_, $rootScope, _$controller_) => {
+ formlyConfig = _formlyConfig_
+ scope = $rootScope.$new()
+ scope.model = {}
+ scope.fields = []
+ scope.options = {}
+ $controller = _$controller_
+ }))
+
+ describe(`fieldTransform`, () => {
+ beforeEach(() => {
+ formlyConfig.extras.fieldTransform = fieldTransform
+ })
+
+ it(`should give a deprecation warning when formlyConfig.extras.fieldTransform is a function rather than an array`, inject(($log) => {
+
+ shouldWarnWithLog(
+ $log,
+ [
+ 'Formly Warning:',
+ 'fieldTransform as a function has been deprecated.',
+ /Attempted for formlyConfig.extras/,
+ ],
+ () => $controller('FormlyFormController', {$scope: scope})
+ )
+ }))
+
+ it(`should give a deprecation warning when options.fieldTransform function rather than an array`, inject(($log) => {
+ formlyConfig.extras.fieldTransform = undefined
+ scope.fields = [getNewField()]
+ scope.options.fieldTransform = fields => fields
+ shouldWarnWithLog(
+ $log,
+ [
+ 'Formly Warning:',
+ 'fieldTransform as a function has been deprecated.',
+ 'Attempted for form',
+ ],
+ () => $controller('FormlyFormController', {$scope: scope})
+ )
+ }))
+
+ it(`should throw an error if something is passed in and nothing is returned`, () => {
+ scope.fields = [getNewField()]
+ scope.options.fieldTransform = function() {
+ // I return nothing...
+ }
+ expect(() => $controller('FormlyFormController', {$scope: scope})).to.throw(/^Formly Error: fieldTransform must return an array of fields/)
+ })
+
+ it(`should allow you to transform field configuration`, () => {
+ scope.options.fieldTransform = fieldTransform
+ const spy = sinon.spy(scope.options, 'fieldTransform')
+ doExpectations(spy)
+ })
+
+ it(`should use formlyConfig.extras.fieldTransform when not specified on options`, () => {
+ const spy = sinon.spy(formlyConfig.extras, 'fieldTransform')
+ doExpectations(spy)
+ })
+
+ it(`should allow you to use an array of transform functions`, () => {
+ scope.fields = [getNewField({
+ customThing: 'foo',
+ otherCustomThing: {
+ whatever: '|-o-|',
+ }})]
+ scope.options.fieldTransform = [fieldTransform]
+ expect(() => $controller('FormlyFormController', {$scope: scope})).to.not.throw()
+
+ const field = scope.fields[0]
+ expect(field).to.have.deep.property('data.customThing')
+ expect(field).to.have.deep.property('data.otherCustomThing')
+ })
+
+ function doExpectations(spy) {
+ const originalFields = [{
+ key: 'keyProp',
+ template: ' ',
+ customThing: 'foo',
+ otherCustomThing: {
+ whatever: '|-o-|',
+ },
+ }]
+ scope.fields = originalFields
+ $controller('FormlyFormController', {$scope: scope})
+ expect(spy).to.have.been.calledWith(originalFields, scope.model, scope.options, scope.form)
+ const field = scope.fields[0]
+
+ expect(field).to.not.have.property('customThing')
+ expect(field).to.not.have.property('otherCustomThing')
+ expect(field).to.have.deep.property('data.customThing')
+ expect(field).to.have.deep.property('data.otherCustomThing')
+ }
+
+ function fieldTransform(fields) {
+ const extraKeys = ['customThing', 'otherCustomThing']
+ return _.map(fields, field => {
+ const newField = {data: {}}
+ _.each(field, (val, name) => {
+ if (_.includes(extraKeys, name)) {
+ newField.data[name] = val
+ } else {
+ newField[name] = val
+ }
+ })
+ return newField
+ })
+ }
+ })
+
+})
diff --git a/src/directives/formly-form.js b/src/directives/formly-form.js
index 541b1b9c..74f411c6 100644
--- a/src/directives/formly-form.js
+++ b/src/directives/formly-form.js
@@ -1,6 +1,6 @@
-import angular from 'angular-fix';
+import angular from 'angular-fix'
-export default formlyForm;
+export default formlyForm
/**
* @ngdoc directive
@@ -9,7 +9,7 @@ export default formlyForm;
*/
// @ngInject
function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpolate) {
- let currentFormId = 1;
+ let currentFormId = 1
return {
restrict: 'AE',
template: formlyFormGetTemplate,
@@ -19,19 +19,19 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
fields: '=',
model: '=',
form: '=?',
- options: '=?'
+ options: '=?',
},
- controller: FormlyFormController,
- link: formlyFormLink
- };
+ controller: 'FormlyFormController',
+ link: formlyFormLink,
+ }
function formlyFormGetTemplate(el, attrs) {
- const rootEl = getRootEl();
- const fieldRootEl = getFieldRootEl();
- const formId = `formly_${currentFormId++}`;
- let parentFormAttributes = '';
+ const rootEl = getRootEl()
+ const fieldRootEl = getFieldRootEl()
+ const formId = `formly_${currentFormId++}`
+ let parentFormAttributes = ''
if (attrs.hasOwnProperty('isFieldGroup') && el.parent().parent().hasClass('formly')) {
- parentFormAttributes = copyAttributes(el.parent().parent()[0].attributes);
+ parentFormAttributes = copyAttributes(el.parent().parent()[0].attributes)
}
return `
<${rootEl} class="formly"
@@ -53,308 +53,74 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
${fieldRootEl}>
${rootEl}>
- `;
+ `
function getRootEl() {
- return attrs.rootEl || 'ng-form';
+ return attrs.rootEl || 'ng-form'
}
function getFieldRootEl() {
- return attrs.fieldRootEl || 'div';
+ return attrs.fieldRootEl || 'div'
}
function getHideDirective() {
- return attrs.hideDirective || formlyConfig.extras.defaultHideDirective || 'ng-if';
+ return attrs.hideDirective || formlyConfig.extras.defaultHideDirective || 'ng-if'
}
function getTrackBy() {
if (!attrs.trackBy) {
- return '';
+ return ''
} else {
- return `track by ${attrs.trackBy}`;
+ return `track by ${attrs.trackBy}`
}
}
function getFormName() {
- let formName = formId;
- const bindName = attrs.bindName;
+ let formName = formId
+ const bindName = attrs.bindName
if (bindName) {
if (angular.version.minor < 3) {
- throw formlyUsability.getFormlyError('bind-name attribute on formly-form not allowed in < angular 1.3');
+ throw formlyUsability.getFormlyError('bind-name attribute on formly-form not allowed in < angular 1.3')
}
// we can do a one-time binding here because we know we're in 1.3.x territory
- formName = `${$interpolate.startSymbol()}::'formly_' + ${bindName}${$interpolate.endSymbol()}`;
+ formName = `${$interpolate.startSymbol()}::'formly_' + ${bindName}${$interpolate.endSymbol()}`
}
- return formName;
+ return formName
}
function getTranscludeClass() {
- return attrs.transcludeClass || '';
+ return attrs.transcludeClass || ''
}
function copyAttributes(attributes) {
const excluded = ['model', 'form', 'fields', 'options', 'name', 'role', 'class',
- 'data-model', 'data-form', 'data-fields', 'data-options', 'data-name'];
- const arrayAttrs = [];
+ 'data-model', 'data-form', 'data-fields', 'data-options', 'data-name']
+ const arrayAttrs = []
angular.forEach(attributes, ({nodeName, value}) => {
if (nodeName !== 'undefined' && excluded.indexOf(nodeName) === -1) {
- arrayAttrs.push(`${toKebabCase(nodeName)}="${value}"`);
+ arrayAttrs.push(`${toKebabCase(nodeName)}="${value}"`)
}
- });
- return arrayAttrs.join(' ');
- }
- }
-
- // @ngInject
- function FormlyFormController($scope, formlyApiCheck, formlyUtil) {
- setupOptions();
- $scope.model = $scope.model || {};
- setupFields();
-
- // watch the model and evaluate watch expressions that depend on it.
- $scope.$watch('model', onModelOrFormStateChange, true);
- if ($scope.options.formState) {
- $scope.$watch('options.formState', onModelOrFormStateChange, true);
- }
-
- function onModelOrFormStateChange() {
- angular.forEach($scope.fields, function runFieldExpressionProperties(field, index) {
- const model = field.model || $scope.model;
- const promise = field.runExpressions && field.runExpressions();
- if (field.hideExpression) { // can't use hide with expressionProperties reliably
- const val = model[field.key];
- field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index);
- }
- if (field.extras && field.extras.validateOnModelChange && field.formControl) {
- const validate = field.formControl.$validate;
- if (promise) {
- promise.then(validate);
- } else {
- validate();
- }
- }
- });
- }
-
- function setupFields() {
- $scope.fields = $scope.fields || [];
-
- checkDeprecatedOptions($scope.options);
-
- let fieldTransforms = $scope.options.fieldTransform || formlyConfig.extras.fieldTransform;
-
- if (!angular.isArray(fieldTransforms)) {
- fieldTransforms = [fieldTransforms];
- }
-
- angular.forEach(fieldTransforms, function transformFields(fieldTransform) {
- if (fieldTransform) {
- $scope.fields = fieldTransform($scope.fields, $scope.model, $scope.options, $scope.form);
- if (!$scope.fields) {
- throw formlyUsability.getFormlyError('fieldTransform must return an array of fields');
- }
- }
- });
-
- setupModels();
-
- angular.forEach($scope.fields, attachKey); // attaches a key based on the index if a key isn't specified
- angular.forEach($scope.fields, setupWatchers); // setup watchers for all fields
- }
-
- function checkDeprecatedOptions(options) {
- if (formlyConfig.extras.fieldTransform && angular.isFunction(formlyConfig.extras.fieldTransform)) {
- formlyWarn(
- 'fieldtransform-as-a-function-deprecated',
- 'fieldTransform as a function has been deprecated.',
- `Attempted for formlyConfig.extras: ${formlyConfig.extras.fieldTransform.name}`,
- formlyConfig.extras
- );
- } else if (options.fieldTransform && angular.isFunction(options.fieldTransform)) {
- formlyWarn(
- 'fieldtransform-as-a-function-deprecated',
- 'fieldTransform as a function has been deprecated.',
- `Attempted for form`,
- options
- );
- }
- }
-
- function setupOptions() {
- formlyApiCheck.throw(
- [formlyApiCheck.formOptionsApi.optional], [$scope.options], {prefix: 'formly-form options check'}
- );
- $scope.options = $scope.options || {};
- $scope.options.formState = $scope.options.formState || {};
-
- angular.extend($scope.options, {
- updateInitialValue,
- resetModel
- });
-
- }
-
- function updateInitialValue() {
- angular.forEach($scope.fields, field => {
- if (isFieldGroup(field) && field.options) {
- field.options.updateInitialValue();
- } else {
- field.updateInitialValue();
- }
- });
- }
-
- function resetModel() {
- angular.forEach($scope.fields, field => {
- if (isFieldGroup(field) && field.options) {
- field.options.resetModel();
- } else if (field.resetModel) {
- field.resetModel();
- }
- });
- }
-
- function setupModels() {
- // a set of field models that are already watched (the $scope.model will have its own watcher)
- const watchedModels = [$scope.model];
-
- if ($scope.options.formState) {
- // $scope.options.formState will have its own watcher
- watchedModels.push($scope.options.formState);
- }
-
- angular.forEach($scope.fields, (field) => {
- const isNewModel = initModel(field);
-
- if (field.model && isNewModel && watchedModels.indexOf(field.model) === -1) {
- $scope.$watch(() => field.model, onModelOrFormStateChange, true);
- watchedModels.push(field.model);
- }
- });
- }
-
- function initModel(field) {
- let isNewModel = true;
-
- if (angular.isString(field.model)) {
- const expression = field.model;
- const index = $scope.fields.indexOf(field);
-
- isNewModel = !refrencesCurrentlyWatchedModel(expression);
-
- field.model = evalCloseToFormlyExpression(expression, undefined, field, index);
- if (!field.model) {
- throw formlyUsability.getFieldError(
- 'field-model-must-be-initialized',
- 'Field model must be initialized. When specifying a model as a string for a field, the result of the' +
- ' expression must have been initialized ahead of time.',
- field);
- }
- }
- return isNewModel;
- }
-
- function refrencesCurrentlyWatchedModel(expression) {
- return ['model', 'formState'].some(item => {
- return formlyUtil.startsWith(expression, `${item}.`) || formlyUtil.startsWith(expression, `${item}[`);
- });
- }
-
- function attachKey(field, index) {
- if (!isFieldGroup(field)) {
- field.key = field.key || index || 0;
- }
- }
-
- function setupWatchers(field, index) {
- if (isFieldGroup(field) || !angular.isDefined(field.watcher)) {
- return;
- }
- let watchers = field.watcher;
- if (!angular.isArray(watchers)) {
- watchers = [watchers];
- }
- angular.forEach(watchers, function setupWatcher(watcher) {
- if (!angular.isDefined(watcher.listener)) {
- throw formlyUsability.getFieldError(
- 'all-field-watchers-must-have-a-listener',
- 'All field watchers must have a listener', field
- );
- }
- const watchExpression = getWatchExpression(watcher, field, index);
- const watchListener = getWatchListener(watcher, field, index);
-
- const type = watcher.type || '$watch';
- watcher.stopWatching = $scope[type](watchExpression, watchListener, watcher.watchDeep);
- });
- }
-
- function getWatchExpression(watcher, field, index) {
- let watchExpression = watcher.expression || `model['${field.key}']`;
- if (angular.isFunction(watchExpression)) {
- // wrap the field's watch expression so we can call it with the field as the first arg
- // and the stop function as the last arg as a helper
- const originalExpression = watchExpression;
- watchExpression = function formlyWatchExpression() {
- const args = modifyArgs(watcher, index, ...arguments);
- return originalExpression(...args);
- };
- watchExpression.displayName = `Formly Watch Expression for field for ${field.key}`;
- }
- return watchExpression;
- }
-
- function getWatchListener(watcher, field, index) {
- let watchListener = watcher.listener;
- if (angular.isFunction(watchListener)) {
- // wrap the field's watch listener so we can call it with the field as the first arg
- // and the stop function as the last arg as a helper
- const originalListener = watchListener;
- watchListener = function formlyWatchListener() {
- const args = modifyArgs(watcher, index, ...arguments);
- return originalListener(...args);
- };
- watchListener.displayName = `Formly Watch Listener for field for ${field.key}`;
- }
- return watchListener;
- }
-
- function modifyArgs(watcher, index, ...originalArgs) {
- return [$scope.fields[index], ...originalArgs, watcher.stopWatching];
- }
-
- function evalCloseToFormlyExpression(expression, val, field, index) {
- const extraLocals = getFormlyFieldLikeLocals(field, index);
- return formlyUtil.formlyEval($scope, expression, val, val, extraLocals);
- }
-
- function getFormlyFieldLikeLocals(field, index) {
- // this makes it closer to what a regular formlyExpression would be
- return {
- options: field,
- index,
- formState: $scope.options.formState,
- formId: $scope.formId
- };
+ })
+ return arrayAttrs.join(' ')
}
}
function formlyFormLink(scope, el, attrs) {
- setFormController();
- fixChromeAutocomplete();
+ setFormController()
+ fixChromeAutocomplete()
function setFormController() {
- const formId = attrs.name;
- scope.formId = formId;
- scope.theFormlyForm = scope[formId];
+ const formId = attrs.name
+ scope.formId = formId
+ scope.theFormlyForm = scope[formId]
if (attrs.form) {
- const getter = $parse(attrs.form);
- const setter = getter.assign;
- const parentForm = getter(scope.$parent);
+ const getter = $parse(attrs.form)
+ const setter = getter.assign
+ const parentForm = getter(scope.$parent)
if (parentForm) {
- scope.theFormlyForm = parentForm;
+ scope.theFormlyForm = parentForm
if (scope[formId]) {
- scope.theFormlyForm.$removeControl(scope[formId]);
+ scope.theFormlyForm.$removeControl(scope[formId])
}
// this next line is probably one of the more dangerous things that angular-formly does to improve the
@@ -366,9 +132,9 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
// https://github.com/formly-js/angular-formly/issues/287
// luckily, this is how the formController has been accessed by the NgModelController since angular 1.0.0
// so I expect it will remain this way for the life of angular 1.x
- el.removeData('$formController');
+ el.removeData('$formController')
} else {
- setter(scope.$parent, scope[formId]);
+ setter(scope.$parent, scope[formId])
}
}
if (!scope.theFormlyForm && !formlyConfig.disableWarnings) {
@@ -378,7 +144,7 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
'Your formly-form does not have a `form` property. Many functions of the form (like validation) may not work',
el,
scope
- );
+ )
}
}
@@ -388,14 +154,14 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
* ლ(ಠ益ಠლ) (╯°□°)╯︵ ┻━┻ (◞‸◟;)
*/
function fixChromeAutocomplete() {
- const global = formlyConfig.extras.removeChromeAutoComplete === true;
- const offInstance = scope.options && scope.options.removeChromeAutoComplete === false;
- const onInstance = scope.options && scope.options.removeChromeAutoComplete === true;
+ const global = formlyConfig.extras.removeChromeAutoComplete === true
+ const offInstance = scope.options && scope.options.removeChromeAutoComplete === false
+ const onInstance = scope.options && scope.options.removeChromeAutoComplete === true
if ((global && !offInstance) || onInstance) {
- const input = document.createElement('input');
- input.setAttribute('autocomplete', 'address-level4');
- input.setAttribute('hidden', 'true');
- el[0].appendChild(input);
+ const input = document.createElement('input')
+ input.setAttribute('autocomplete', 'address-level4')
+ input.setAttribute('hidden', 'true')
+ el[0].appendChild(input)
}
}
@@ -405,13 +171,10 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
// stateless util functions
function toKebabCase(string) {
if (string) {
- return string.replace(/([A-Z])/g, $1 => '-' + $1.toLowerCase());
+ return string.replace(/([A-Z])/g, $1 => '-' + $1.toLowerCase())
} else {
- return '';
+ return ''
}
}
- function isFieldGroup(field) {
- return field && !!field.fieldGroup;
- }
}
diff --git a/src/directives/formly-form.test.js b/src/directives/formly-form.test.js
index dbe5c801..fb767c2c 100644
--- a/src/directives/formly-form.test.js
+++ b/src/directives/formly-form.test.js
@@ -2,99 +2,98 @@
/* eslint no-console:0 */
/* eslint max-len:0 */
/* eslint max-nested-callbacks:0 */
-import testUtils from '../test.utils.js';
-import angular from 'angular-fix';
-import _ from 'lodash';
+import testUtils from '../test.utils.js'
+import angular from 'angular-fix'
-const {getNewField, input, basicForm, shouldWarnWithLog} = testUtils;
+const {getNewField, input, basicForm} = testUtils
describe('formly-form', () => {
- let $compile, formlyConfig, scope, el, $timeout;
+ let $compile, formlyConfig, scope, el, $timeout
- beforeEach(window.module('formly'));
+ beforeEach(window.module('formly'))
beforeEach(inject((_$compile_, _formlyConfig_, _$timeout_, $rootScope) => {
- formlyConfig = _formlyConfig_;
- $compile = _$compile_;
- $timeout = _$timeout_;
- scope = $rootScope.$new();
- scope.model = {};
- scope.fields = [];
- }));
+ formlyConfig = _formlyConfig_
+ $compile = _$compile_
+ $timeout = _$timeout_
+ scope = $rootScope.$new()
+ scope.model = {}
+ scope.fields = []
+ }))
it(`should be possible to use it as an attribute directive`, () => {
const el = compileAndDigest(`
- `);
- expect(el.length).to.equal(1);
- expect(el.prop('nodeName').toLowerCase()).to.equal('ng-form');
- });
+ `)
+ expect(el.length).to.equal(1)
+ expect(el.prop('nodeName').toLowerCase()).to.equal('ng-form')
+ })
it('should use ng-form as the default root tag', () => {
const el = compileAndDigest(`
- `);
- expect(el.length).to.equal(1);
- expect(el.prop('nodeName').toLowerCase()).to.equal('ng-form');
- });
+ `)
+ expect(el.length).to.equal(1)
+ expect(el.prop('nodeName').toLowerCase()).to.equal('ng-form')
+ })
it('should use a different root tag when specified', () => {
const el = compileAndDigest(`
- `);
- expect(el.length).to.equal(1);
- expect(el.prop('nodeName').toLowerCase()).to.equal('form');
- });
+ `)
+ expect(el.length).to.equal(1)
+ expect(el.prop('nodeName').toLowerCase()).to.equal('form')
+ })
it(`should use a different root tag for formly-fields when specified`, () => {
- scope.fields = [getNewField()];
+ scope.fields = [getNewField()]
const el = compileAndDigest(`
- `);
- expect(el[0].querySelector('area.formly-field')).to.exist;
- });
+ `)
+ expect(el[0].querySelector('area.formly-field')).to.exist
+ })
it(`should assign the scope's "form" property to the given FormController if it has a value`, () => {
const el = compileAndDigest(`
- `);
- const isolateScope = angular.element(el[0].querySelector('#my-formly-form')).isolateScope();
- expect(scope.theForm).to.eq(isolateScope.form);
- expect(scope.theForm.$name).to.eq('theForm');
- });
+ `)
+ const isolateScope = angular.element(el[0].querySelector('#my-formly-form')).isolateScope()
+ expect(scope.theForm).to.eq(isolateScope.form)
+ expect(scope.theForm.$name).to.eq('theForm')
+ })
it(`should assign the scope's "form" property to its own FormController if it doesn't have a value`, () => {
const el = compileAndDigest(`
- `);
- const isolateScope = angular.element(el[0].querySelector('#my-formly-form')).isolateScope();
- expect(scope.theForm).to.eq(isolateScope.theFormlyForm);
- });
+ `)
+ const isolateScope = angular.element(el[0].querySelector('#my-formly-form')).isolateScope()
+ expect(scope.theForm).to.eq(isolateScope.theFormlyForm)
+ })
it(`should warn if there's no FormController to be assigned`, inject(($log) => {
compileAndDigest(`
- `);
- const log = $log.warn.logs[0];
- expect($log.warn.logs).to.have.length(1);
- expect(log[0]).to.equal('Formly Warning:');
- expect(log[1]).to.equal('Your formly-form does not have a `form` property. Many functions of the form (like validation) may not work');
- }));
+ `)
+ const log = $log.warn.logs[0]
+ expect($log.warn.logs).to.have.length(1)
+ expect(log[0]).to.equal('Formly Warning:')
+ expect(log[1]).to.equal('Your formly-form does not have a `form` property. Many functions of the form (like validation) may not work')
+ }))
it(`should put the formControl on the field's scope when using a different form root element`, () => {
- scope.fields = [getNewField()];
+ scope.fields = [getNewField()]
const el = compileAndDigest(`
- `);
+ `)
- const fieldScope = angular.element(el[0].querySelector('.formly-field')).isolateScope();
- expect(fieldScope.fc).to.exist;
- });
+ const fieldScope = angular.element(el[0].querySelector('.formly-field')).isolateScope()
+ expect(fieldScope.fc).to.exist
+ })
it(`should not allow sibling forms to override each other on a parent form`, () => {
compileAndDigest(`
@@ -102,36 +101,36 @@ describe('formly-form', () => {
- `);
- expect(scope.parent).to.have.property('formly_1');
- expect(scope.parent).to.have.property('formly_2');
- });
+ `)
+ expect(scope.parent).to.have.property('formly_1')
+ expect(scope.parent).to.have.property('formly_2')
+ })
it(`should place the form control on the scope property defined by the form attribute`, () => {
compileAndDigest(`
- `);
- expect(scope.vm).to.have.property('myForm');
- expect(scope.vm.myForm).to.have.property('$name');
- });
+ `)
+ expect(scope.vm).to.have.property('myForm')
+ expect(scope.vm.myForm).to.have.property('$name')
+ })
it(`should initialize the model and the fields if not provided`, () => {
compileAndDigest(`
- `);
- expect(scope.model).to.exist;
- expect(scope.fields).to.exist;
- });
+ `)
+ expect(scope.model).to.exist
+ expect(scope.fields).to.exist
+ })
it(`should initialize the model and fields if they are null`, () => {
- scope.model = null;
- scope.fields = null;
+ scope.model = null
+ scope.fields = null
compileAndDigest(`
- `);
- expect(scope.model).to.exist;
- expect(scope.fields).to.exist;
- });
+ `)
+ expect(scope.model).to.exist
+ expect(scope.fields).to.exist
+ })
it(`should allow the user to specify their own name for the form`, () => {
compileAndDigest(`
@@ -140,57 +139,57 @@ describe('formly-form', () => {
- `);
+ `)
- expect(scope.parent).to.have.property('formly_0_in_my_ng_repeat');
- expect(scope.parent).to.have.property('formly_1_in_my_ng_repeat');
- const firstForm = el[0].querySelector('ng-form');
- const firstFormScope = angular.element(firstForm).isolateScope();
- expect(firstFormScope.formId).to.eq('formly_0_in_my_ng_repeat');
- });
+ expect(scope.parent).to.have.property('formly_0_in_my_ng_repeat')
+ expect(scope.parent).to.have.property('formly_1_in_my_ng_repeat')
+ const firstForm = el[0].querySelector('ng-form')
+ const firstFormScope = angular.element(firstForm).isolateScope()
+ expect(firstFormScope.formId).to.eq('formly_0_in_my_ng_repeat')
+ })
it(`should allow you to completely swap out the fields`, () => {
- scope.fields = [getNewField(), getNewField()];
- compileAndDigest(basicForm);
- scope.fields = [getNewField(), getNewField()];
+ scope.fields = [getNewField(), getNewField()]
+ compileAndDigest(basicForm)
+ scope.fields = [getNewField(), getNewField()]
- expect(() => scope.$digest()).to.not.throw();
- });
+ expect(() => scope.$digest()).to.not.throw()
+ })
describe(`ngTransclude element`, () => {
it(`should have the specified className`, () => {
const el = compileAndDigest(`
- `);
- expect(el[0].querySelector('.foo.yeah')).to.exist;
- });
+ `)
+ expect(el[0].querySelector('.foo.yeah')).to.exist
+ })
it(`should not have a className when one is unspecified`, () => {
// this test is to avoid giving it a class of "undefined"
const el = compileAndDigest(`
- `);
- const transcludedDiv = el[0].querySelector('div[ng-transclude]');
- expect(transcludedDiv.classList).to.have.length(0);
- });
- });
+ `)
+ const transcludedDiv = el[0].querySelector('div[ng-transclude]')
+ expect(transcludedDiv.classList).to.have.length(0)
+ })
+ })
describe(`fieldGroup`, () => {
beforeEach(() => {
- scope.user = {};
+ scope.user = {}
formlyConfig.setType({
name: 'input',
- template: input
- });
- let key = 0;
+ template: input,
+ })
+ let key = 0
scope.fields = [
{
className: 'bar',
fieldGroup: [
{type: 'input', key: key++},
- {type: 'input', key: key++}
- ]
+ {type: 'input', key: key++},
+ ],
},
{type: 'input', key: key++},
{type: 'input', key: key++},
@@ -200,22 +199,22 @@ describe('formly-form', () => {
fieldGroup: [
{type: 'input', key: key++},
{type: 'input', key: key++, className: 'specific-field'},
- {type: 'input', key: key++}
- ]
- }
- ];
- });
+ {type: 'input', key: key++},
+ ],
+ },
+ ]
+ })
it(`should allow you to specify a fieldGroup which will use the formly-form directive internally`, () => {
- compileAndDigest();
+ compileAndDigest()
- expect(el[0].querySelectorAll('[formly-field].formly-field-input')).to.have.length(7);
+ expect(el[0].querySelectorAll('[formly-field].formly-field-input')).to.have.length(7)
- expect(el[0].querySelectorAll('ng-form')).to.have.length(2);
- expect(el[0].querySelectorAll('ng-form.foo')).to.have.length(1);
- expect(el[0].querySelectorAll('ng-form.foo [formly-field].formly-field-input')).to.have.length(3);
- expect(el[0].querySelectorAll('.formly-field-group')).to.have.length(2);
- });
+ expect(el[0].querySelectorAll('ng-form')).to.have.length(2)
+ expect(el[0].querySelectorAll('ng-form.foo')).to.have.length(1)
+ expect(el[0].querySelectorAll('ng-form.foo [formly-field].formly-field-input')).to.have.length(3)
+ expect(el[0].querySelectorAll('.formly-field-group')).to.have.length(2)
+ })
it(`should copy the parent's attributes in the template`, () => {
scope.fields = [
@@ -223,94 +222,144 @@ describe('formly-form', () => {
className: 'field-group',
fieldGroup: [
getNewField(),
- getNewField()
- ]
- }
- ];
+ getNewField(),
+ ],
+ },
+ ]
- compileAndDigest(' ');
+ compileAndDigest(' ')
- const fieldGroupNode = el[0].querySelector('.field-group');
- expect(fieldGroupNode).to.exist;
+ const fieldGroupNode = el[0].querySelector('.field-group')
+ expect(fieldGroupNode).to.exist
- expect(fieldGroupNode.getAttribute('some-extra-attr')).to.eq('someValue');
- });
+ expect(fieldGroupNode.getAttribute('some-extra-attr')).to.eq('someValue')
+ })
describe(`options`, () => {
- const formWithOptions = ' ';
+ const formWithOptions = ' '
beforeEach(() => {
scope.fields = [
{
className: 'field-group',
fieldGroup: [
getNewField(),
- getNewField()
- ]
+ getNewField(),
+ ],
},
{
className: 'field-group',
fieldGroup: [
getNewField(),
- getNewField()
- ]
- }
- ];
+ getNewField(),
+ ],
+ },
+ ]
- scope.options = {};
- });
+ scope.options = {}
+ })
it(`should allow you to call the child's updateInitialValue and resetModel from the parent`, () => {
- const field = scope.fields[0].fieldGroup[0];
- compileAndDigest(formWithOptions);
- expect(field.initialValue).to.not.exist;
- scope.model[field.key] = 'foo';
- scope.options.updateInitialValue();
- expect(field.initialValue).to.eq('foo');
- scope.model[field.key] = 'bar';
- scope.options.resetModel();
- expect(scope.model[field.key]).to.eq('foo');
- });
+ const field = scope.fields[0].fieldGroup[0]
+ compileAndDigest(formWithOptions)
+ expect(field.initialValue).to.not.exist
+ scope.model[field.key] = 'foo'
+ scope.options.updateInitialValue()
+ expect(field.initialValue).to.eq('foo')
+ scope.model[field.key] = 'bar'
+ scope.options.resetModel()
+ expect(scope.model[field.key]).to.eq('foo')
+ })
it(`should have the same formState`, () => {
- compileAndDigest(formWithOptions);
- const fieldGroup1 = scope.fields[0];
- const fieldGroup2 = scope.fields[1];
- expect(fieldGroup1.options.formState).to.eq(fieldGroup2.options.formState);
- expect(scope.options.formState).to.eq(fieldGroup1.options.formState);
- });
- });
+ compileAndDigest(formWithOptions)
+ const fieldGroup1 = scope.fields[0]
+ const fieldGroup2 = scope.fields[1]
+ expect(fieldGroup1.options.formState).to.eq(fieldGroup2.options.formState)
+ expect(scope.options.formState).to.eq(fieldGroup1.options.formState)
+ })
+ })
+
+ it(`should be possible to use a wrapper & templateOptions in a fieldGroup`, () => {
+ formlyConfig.setWrapper({
+ name: 'panel',
+ template: `
+
+ Panel Title: {{options.templateOptions.title}}
+
+
+ Subtitle: {{to.subtitle}}
+
+
+
+
+
+ `,
+ })
+
+ scope.fields = [
+ {
+ className: 'field-group',
+ wrapper: 'panel',
+ templateOptions: {
+ title: 'My Panel',
+ subtitle: 'is awesome',
+ },
+ fieldGroup: [
+ getNewField(),
+ getNewField(),
+ ],
+ },
+ ]
+
+ scope.options = {}
+
+ compileAndDigest()
+
+ const panelNode = el[0].querySelector('.panel')
+ expect(panelNode).to.exist
+ const bodyNode = panelNode.querySelector('.body')
+ expect(bodyNode).to.exist
+ const headingNode = panelNode.querySelector('.heading')
+ expect(headingNode).to.exist
+ const headingEl = angular.element(headingNode)
+ expect(headingEl.text().trim()).to.eq('Panel Title: My Panel')
+ const subHeadingNode = panelNode.querySelector('.sub-heading')
+ expect(subHeadingNode).to.exist
+ const subHeadingEl = angular.element(subHeadingNode)
+ expect(subHeadingEl.text().trim()).to.eq('Subtitle: is awesome')
+ })
it(`should be possible to hide a fieldGroup with the hide property`, () => {
- compileAndDigest();
+ compileAndDigest()
- expect(el[0].querySelectorAll('ng-form.bar')).to.have.length(1);
+ expect(el[0].querySelectorAll('ng-form.bar')).to.have.length(1)
- const fieldGroup1 = scope.fields[0];
- fieldGroup1.hide = true;
+ const fieldGroup1 = scope.fields[0]
+ fieldGroup1.hide = true
- scope.$digest();
+ scope.$digest()
- expect(el[0].querySelectorAll('ng-form.bar')).to.have.length(0);
- });
+ expect(el[0].querySelectorAll('ng-form.bar')).to.have.length(0)
+ })
it(`should pass the model to it's children fields`, () => {
- compileAndDigest();
-
- const specificGroup = scope.fields[3];
- const specificField = specificGroup.fieldGroup[1];
- const specificFieldNode = el[0].querySelector('.specific-field');
- expect(specificFieldNode).to.exist;
- specificField.formControl.$setViewValue('foo');
- expect(specificGroup.model[specificField.key]).to.eq('foo');
- expect(specificGroup.model).to.eq(scope.user);
- expect(scope.user[specificField.key]).to.eq('foo');
- expect(angular.element(specificFieldNode).isolateScope().model).to.eq(scope.user);
- });
+ compileAndDigest()
+
+ const specificGroup = scope.fields[3]
+ const specificField = specificGroup.fieldGroup[1]
+ const specificFieldNode = el[0].querySelector('.specific-field')
+ expect(specificFieldNode).to.exist
+ specificField.formControl.$setViewValue('foo')
+ expect(specificGroup.model[specificField.key]).to.eq('foo')
+ expect(specificGroup.model).to.eq(scope.user)
+ expect(scope.user[specificField.key]).to.eq('foo')
+ expect(angular.element(specificFieldNode).isolateScope().model).to.eq(scope.user)
+ })
it(`should have a form property`, () => {
- compileAndDigest();
- expect(scope.fields[0].form).to.have.property('$$parentForm');
- });
+ compileAndDigest()
+ expect(scope.fields[0].form).to.have.property('$$parentForm')
+ })
it(`should be able to be dynamically hidden with a hideExpression`, () => {
scope.fields = [
@@ -318,26 +367,26 @@ describe('formly-form', () => {
hideExpression: 'model.foo === "bar"',
fieldGroup: [
getNewField(),
- getNewField()
- ]
+ getNewField(),
+ ],
},
getNewField({
- key: 'foo', hideExpression: 'options.data.canHide && model.baz === "foobar"', data: {canHide: true}
- })
- ];
+ key: 'foo', hideExpression: 'options.data.canHide && model.baz === "foobar"', data: {canHide: true},
+ }),
+ ]
- compileAndDigest();
+ compileAndDigest()
- expect(scope.fields[0].hide).to.be.false;
- expect(scope.fields[1].hide).to.be.false;
+ expect(scope.fields[0].hide).to.be.false
+ expect(scope.fields[1].hide).to.be.false
- scope.model.foo = 'bar';
- scope.model.baz = 'foobar';
- scope.$digest();
+ scope.model.foo = 'bar'
+ scope.model.baz = 'foobar'
+ scope.$digest()
- expect(scope.fields[0].hide).to.be.true;
- expect(scope.fields[1].hide).to.be.true;
- });
+ expect(scope.fields[0].hide).to.be.true
+ expect(scope.fields[1].hide).to.be.true
+ })
it(`should allow a field group inside a field group`, () => {
scope.fields = scope.fields = [
@@ -350,15 +399,15 @@ describe('formly-form', () => {
className: 'field-group',
fieldGroup: [
getNewField(),
- getNewField()
- ]
- }
- ]
- }
- ];
+ getNewField(),
+ ],
+ },
+ ],
+ },
+ ]
- expect(() => compileAndDigest()).to.not.throw();
- });
+ expect(() => compileAndDigest()).to.not.throw()
+ })
it(`should validate fields in a fieldGroup`, () => {
scope.fields = [
@@ -372,535 +421,940 @@ describe('formly-form', () => {
fieldGroup: [
getNewField({extra: 'property'}),
getNewField(),
- getNewField()
- ]
- }
- ]
- }
- ];
- expect(() => compileAndDigest()).to.throw();
- });
+ getNewField(),
+ ],
+ },
+ ],
+ },
+ ]
+ expect(() => compileAndDigest()).to.throw()
+ })
- });
+ })
describe('an instance of model', () => {
- const spy1 = sinon.spy();
- const spy2 = sinon.spy();
+ const spy1 = sinon.spy()
+ const spy2 = sinon.spy()
beforeEach(() => {
- scope.model = {};
- scope.fieldModel1 = {};
+ scope.model = {}
+ scope.fieldModel1 = {}
scope.fields = [
{template: input, key: 'foo', model: scope.fieldModel1},
{template: input, key: 'bar', model: scope.fieldModel1},
- {template: input, key: 'zoo', model: scope.fieldModel1}
- ];
- });
+ {template: input, key: 'zoo', model: scope.fieldModel1},
+ {template: input, key: 'test'},
+ ]
+ })
it('should be assigned with only one watcher', () => {
- compileAndDigest();
- $timeout.flush();
+ compileAndDigest()
+ $timeout.flush()
+
+ scope.fields[0].expressionProperties = {'data.dummy': spy1}
+ scope.fields[1].expressionProperties = {'data.dummy': spy2}
+
+ scope.fieldModel1.foo = 'value'
+ scope.$digest()
+ $timeout.flush()
+
+ expect(spy1).to.have.been.calledOnce
+ expect(spy2).to.have.been.calledOnce
+ })
+
+ it('should be updated when the reference to the model changes', () => {
+ scope.model = {test: 'bar'}
+ scope.fields[3].expressionProperties = {'data.test': 'model.test'}
- scope.fields[0].expressionProperties = {'data.dummy': spy1};
- scope.fields[1].expressionProperties = {'data.dummy': spy2};
+ compileAndDigest()
+ $timeout.flush()
- scope.fieldModel1.foo = 'value';
- scope.$digest();
- $timeout.flush();
+ scope.model = {test: 'baz'}
- expect(spy1).to.have.been.calledOnce;
- expect(spy2).to.have.been.calledOnce;
- });
- });
+ scope.$digest()
+ $timeout.flush()
+
+ expect(scope.fields[3].data.test).to.equal('baz')
+ })
+ })
describe('nested model as string', () => {
- let spy;
+ let spy
beforeEach(() => {
- spy = sinon.spy();
+ spy = sinon.spy()
scope.model = {
- nested: {}
- };
+ nested: {},
+ }
scope.fields = [
- {template: input, key: 'foo'}
- ];
- });
+ {template: input, key: 'foo'},
+ ]
+ })
it('starting with "model." should be assigned with only one watcher', () => {
- testModelAccessor('model.nested');
- });
+ testModelAccessor('model.nested')
+ })
it('starting with "model[" should be assigned with only one watcher', () => {
- testModelAccessor('model["nested"]');
- });
+ testModelAccessor('model["nested"]')
+ })
it('starting with "formState." should be assigned with only one watcher', () => {
- testFormStateAccessor('formState.nested');
- });
+ testFormStateAccessor('formState.nested')
+ })
it('starting with "formState[" should be assigned with only one watcher', () => {
- testFormStateAccessor('formState["nested"]');
- });
+ testFormStateAccessor('formState["nested"]')
+ })
+
+ it('should be updated when the reference to the outer model changes', () => {
+ scope.model.nested.foo = 'bar'
+ scope.fields[0].model = 'model.nested'
+ scope.fields[0].expressionProperties = {'data.foo': 'model.foo'}
+
+ compileAndDigest()
+ $timeout.flush()
+
+ scope.model = {
+ nested: {
+ foo: 'baz',
+ },
+ }
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(scope.fields[0].data.foo).to.equal('baz')
+ })
function testModelAccessor(accessor) {
- scope.fields[0].model = accessor;
+ scope.fields[0].model = accessor
- compileAndDigest();
- $timeout.flush();
+ compileAndDigest()
+ $timeout.flush()
- scope.fields[0].expressionProperties = {'data.dummy': spy};
+ scope.fields[0].expressionProperties = {'data.dummy': spy}
- scope.model.nested.foo = 'value';
- scope.$digest();
- $timeout.flush();
+ scope.model.nested.foo = 'value'
+ scope.$digest()
+ $timeout.flush()
- expect(spy).to.have.been.calledOnce;
+ expect(spy).to.have.been.calledOnce
}
function testFormStateAccessor(accessor) {
- const formWithOptions = ' ';
+ const formWithOptions = ' '
scope.options = {
formState: {
- nested: {}
- }
- };
- scope.fields[0].model = accessor;
+ nested: {},
+ },
+ }
+ scope.fields[0].model = accessor
- compileAndDigest(formWithOptions);
- $timeout.flush();
+ compileAndDigest(formWithOptions)
+ $timeout.flush()
- scope.fields[0].expressionProperties = {'data.dummy': spy};
+ scope.fields[0].expressionProperties = {'data.dummy': spy}
- scope.options.formState.nested.foo = 'value';
- scope.$digest();
- $timeout.flush();
+ scope.options.formState.nested.foo = 'value'
+ scope.$digest()
+ $timeout.flush()
- expect(spy).to.have.been.calledOnce;
+ expect(spy).to.have.been.calledOnce
}
- });
+ })
describe('hideExpression', () => {
beforeEach(() => {
- scope.model = {};
- scope.fieldModel = {};
+ scope.model = {}
+ scope.fieldModel = {}
+ })
+ describe('behaviour when model changes', () => {
+
+ describe('when a string is passed to hideExpression', () => {
+ beforeEach(() => {
+ scope.fields = [
+ {template: input, key: 'foo', model: scope.fieldModel},
+ {template: input, key: 'bar', model: scope.fieldModel, hideExpression: () => !!scope.fieldModel.foo},
+ ]
+ })
- scope.fields = [
- {template: input, key: 'foo', model: scope.fieldModel},
- {template: input, key: 'bar', model: scope.fieldModel, hideExpression: () => !!scope.fieldModel.foo}
- ];
- });
-
- it('should be called and resolve to true when field model changes', () => {
- compileAndDigest();
- expect(scope.fields[1].hide).be.false;
- scope.fields[0].formControl.$setViewValue('value');
- expect(scope.fields[1].hide).be.true;
- });
-
- it('should be called and resolve to false when field model changes', () => {
- scope.fieldModel.foo = 'value';
- compileAndDigest();
- expect(scope.fields[1].hide).be.true;
- scope.fields[0].formControl.$setViewValue('');
- expect(scope.fields[1].hide).be.false;
- });
- });
+ it('should be called and should resolve to true when field model changes', () => {
+ compileAndDigest()
+ expect(scope.fields[1].hide).be.false
+ scope.fields[0].formControl.$setViewValue('value')
+ expect(scope.fields[1].hide).be.true
+ })
+
+ it('should be called and should resolve to false when field model changes', () => {
+ scope.fieldModel.foo = 'value'
+ compileAndDigest()
+ expect(scope.fields[1].hide).be.true
+ scope.fields[0].formControl.$setViewValue('')
+ expect(scope.fields[1].hide).be.false
+ })
+ })
+ describe('when a function is passed to hideExpression', () => {
+ beforeEach(() => {
+ scope.fields = [
+ {template: input, key: 'foo', model: scope.fieldModel},
+ {
+ template: input, key: 'bar',
+ model: scope.fieldModel,
+ hideExpression: ($viewValue, $modelValue, scope) => {
+ return !!scope.fields[1].data.parentScope.fieldModel.foo //since the scope passed to the function belongs to the form,
+ }, //we store the outer(parent) scope in 'data' property to access
+ data: { //the template named 'foo' stored in the fields array
+ parentScope: scope, //the parent scope(one used to compile the form)
+ },
+ },
+ ]
+ })
+
+ it('should be called and should resolve to true when field model changes', () => {
+ compileAndDigest()
+ expect(scope.fields[1].hide).be.false
+ scope.fields[0].formControl.$setViewValue('value')
+ expect(scope.fields[1].hide).be.true
+ })
+
+ it('should be called and should resolve to false when field model changes', () => {
+ scope.fieldModel.foo = 'value'
+ compileAndDigest()
+ expect(scope.fields[1].hide).be.true
+ scope.fields[0].formControl.$setViewValue('')
+ expect(scope.fields[1].hide).be.false
+ })
+ })
+ })
+
+ it('ensures that hideExpression has all the expressionProperties values', () => {
+ scope.model = {nested: {foo: 'bar', baz: []}}
+ scope.options = {formState: {}}
+ scope.fields = [{
+ template: input,
+ key: 'test',
+ model: 'model.nested',
+ hideExpression: `
+ model === options.data.model &&
+ options === options.data.field &&
+ index === 0 &&
+ formState === options.data.formOptions.formState &&
+ originalModel === options.data.originalModel &&
+ formOptions === options.data.formOptions`,
+ data: {
+ model: scope.model.nested,
+ originalModel: scope.model,
+ formOptions: scope.options,
+ },
+ }]
+ scope.fields[0].data.field = scope.fields[0]
+ compileAndDigest()
+ expect(scope.fields[0].hide).be.true
+ })
+ })
describe(`options`, () => {
beforeEach(() => {
scope.model = {
foo: 'myFoo',
bar: 123,
- foobar: 'ab@cd.com'
- };
+ foobar: 'ab@cd.com',
+ }
scope.fields = [
{template: input, key: 'foo'},
{template: input, key: 'bar', templateOptions: {type: 'numaber'}},
- {template: input, key: 'foobar', templateOptions: {type: 'email'}}
- ];
+ {template: input, key: 'foobar', templateOptions: {type: 'email'}},
+ ]
scope.options = {
formState: {
- foo: 'bar'
- }
- };
- });
+ foo: 'bar',
+ },
+ }
+ })
it(`should throw an error with extra options`, () => {
expect(() => {
- scope.options = {extra: true};
- compileAndDigest();
- }).to.throw();
- });
+ scope.options = {extra: true}
+ compileAndDigest()
+ }).to.throw()
+ })
it(`should run expressionProperties when the formState changes`, () => {
- const spy = sinon.spy();
+ const spy = sinon.spy()
const field = {
template: input,
key: 'foo',
expressionProperties: {
- 'templateOptions.label': spy
- }
- };
- scope.fields = [field];
- compileAndDigest();
- scope.options.formState.foo = 'eggs';
- scope.$digest();
- $timeout.flush();
- expect(spy).to.have.been.called;
- });
+ 'templateOptions.label': spy,
+ },
+ }
+ scope.fields = [field]
+ compileAndDigest()
+ scope.options.formState.foo = 'eggs'
+ scope.$digest()
+ $timeout.flush()
+ expect(spy).to.have.been.called
+ })
describe(`resetModel`, () => {
it(`should reset the model that's given`, () => {
- compileAndDigest();
- expect(typeof scope.options.resetModel).to.eq('function');
- const previousFoo = scope.model.foo;
- scope.model.foo = 'newFoo';
- scope.options.resetModel();
- expect(scope.model.foo).to.eq(previousFoo);
- });
+ compileAndDigest()
+ expect(typeof scope.options.resetModel).to.eq('function')
+ const previousFoo = scope.model.foo
+ scope.model.foo = 'newFoo'
+ scope.options.resetModel()
+ expect(scope.model.foo).to.eq(previousFoo)
+ })
it(`should reset the $viewValue of fields`, () => {
- compileAndDigest();
- const previousFoobar = scope.model.foobar;
- scope.fields[2].formControl.$setViewValue('not-an-email');
- scope.options.resetModel();
- expect(scope.fields[2].formControl.$viewValue).to.equal(previousFoobar);
- });
+ compileAndDigest()
+ const previousFoobar = scope.model.foobar
+ scope.fields[2].formControl.$setViewValue('not-an-email')
+ scope.options.resetModel()
+ expect(scope.fields[2].formControl.$viewValue).to.equal(previousFoobar)
+ })
it(`should reset the $viewValue and $modelValue to undefined if the value was not originally defined`, () => {
scope.fields.push({
- template: input, key: 'baz', templateOptions: {required: true}
- });
- compileAndDigest();
- const fc = scope.fields[scope.fields.length - 1].formControl;
- scope.model.baz = 'hello world';
- scope.$digest();
- expect(fc.$viewValue).to.eq('hello world');
- expect(fc.$modelValue).to.eq('hello world');
- scope.options.resetModel();
- expect(scope.model.baz).to.be.undefined;
- expect(fc.$viewValue).to.be.undefined;
- expect(fc.$modelValue).to.be.undefined;
- });
+ template: input, key: 'baz', templateOptions: {required: true},
+ })
+ compileAndDigest()
+ const fc = scope.fields[scope.fields.length - 1].formControl
+ scope.model.baz = 'hello world'
+ scope.$digest()
+ expect(fc.$viewValue).to.eq('hello world')
+ expect(fc.$modelValue).to.eq('hello world')
+ scope.options.resetModel()
+ expect(scope.model.baz).to.be.undefined
+ expect(fc.$viewValue).to.be.undefined
+ expect(fc.$modelValue).to.be.undefined
+ })
it(`should rerender the ng-model element`, () => {
- const el = compileAndDigest();
- const ngModelNode = el[0].querySelector('[ng-model]');
- scope.model.foo = 'hey there!';
- scope.$digest();
- scope.options.resetModel();
- expect(ngModelNode.value).to.eq('myFoo');
- });
+ const el = compileAndDigest()
+ const ngModelNode = el[0].querySelector('[ng-model]')
+ scope.model.foo = 'hey there!'
+ scope.$digest()
+ scope.options.resetModel()
+ expect(ngModelNode.value).to.eq('myFoo')
+ })
it(`should reset models of fields`, () => {
- scope.fieldModel = {baz: false};
+ scope.fieldModel = {baz: false}
scope.fields.push({
- template: input, key: 'baz', model: scope.fieldModel
- });
+ template: input, key: 'baz', model: scope.fieldModel,
+ })
- compileAndDigest();
+ compileAndDigest()
- scope.fieldModel.baz = true;
- scope.options.resetModel();
- expect(scope.fieldModel.baz).to.be.false;
- });
+ scope.fieldModel.baz = true
+ scope.options.resetModel()
+ expect(scope.fieldModel.baz).to.be.false
+ })
it(`should not break if a fieldGroup has yet to be initialized`, () => {
scope.fields = [
- {fieldGroup: [getNewField()], hide: true}
- ];
- compileAndDigest();
- expect(() => scope.options.resetModel()).to.not.throw();
- });
+ {fieldGroup: [getNewField()], hide: true},
+ ]
+ compileAndDigest()
+ expect(() => scope.options.resetModel()).to.not.throw()
+ })
it(`should not break if a field has yet to be initialized`, () => {
- scope.fields = [getNewField({hide: true})];
- compileAndDigest();
- expect(() => scope.options.resetModel()).to.not.throw();
- });
- });
+ scope.fields = [getNewField({hide: true})]
+ compileAndDigest()
+ expect(() => scope.options.resetModel()).to.not.throw()
+ })
+ })
describe(`hide-directive attribute`, () => {
beforeEach(() => {
- scope.fields = [{template: input, key: 'foo'}];
- });
+ scope.fields = [{template: input, key: 'foo'}]
+ })
it(`should default to ng-if`, () => {
- compileAndDigest(basicForm);
- const fieldNode = el[0].querySelector('.formly-field');
- expect(fieldNode.getAttribute('ng-if')).to.exist;
- });
+ compileAndDigest(basicForm)
+ const fieldNode = el[0].querySelector('.formly-field')
+ expect(fieldNode.getAttribute('ng-if')).to.exist
+ })
it(`should allow custom directive for hiding`, () => {
compileAndDigest(`
- `);
- const fieldNode = el[0].querySelector('.formly-field');
- expect(fieldNode.getAttribute('ng-if')).to.not.exist;
- expect(fieldNode.getAttribute('ng-show')).to.exist;
- });
+ `)
+ const fieldNode = el[0].querySelector('.formly-field')
+ expect(fieldNode.getAttribute('ng-if')).to.not.exist
+ expect(fieldNode.getAttribute('ng-show')).to.exist
+ })
- });
+ })
describe(`track-by attribute`, () => {
- const template = ` `;
+ const template = ` `
beforeEach(() => {
- scope.fields = [getNewField(), getNewField(), getNewField()];
- });
+ scope.fields = [getNewField(), getNewField(), getNewField()]
+ })
it(`should default to track by $$hashKey when the attribute is not present`, () => {
- compileAndDigest(basicForm);
- expect(scope.fields[0].$$hashKey).to.exist;
- });
+ compileAndDigest(basicForm)
+ expect(scope.fields[0].$$hashKey).to.exist
+ })
it(`should track by the specified value`, () => {
- compileAndDigest(template);
- expectTrackBy('field.key');
- });
+ compileAndDigest(template)
+ expectTrackBy('field.key')
+ })
it(`should allow you to track by $index`, () => {
- compileAndDigest(` `);
- expectTrackBy('$index');
- });
+ compileAndDigest(` `)
+ expectTrackBy('$index')
+ })
it(`should throw an error when the field's specified values are not unique`, () => {
- scope.fields.push({template: input, key: 'foo'});
- scope.fields.push({template: input, key: 'foo'});
- expect(compileAndDigest.bind(null, template)).to.throw('ngRepeat:dupes');
- });
+ scope.fields.push({template: input, key: 'foo'})
+ scope.fields.push({template: input, key: 'foo'})
+ expect(compileAndDigest.bind(null, template)).to.throw('ngRepeat:dupes')
+ })
it(`should allow you to push a field after initial compile`, () => {
- expectFieldChange(scope.fields.push.bind(scope.fields, getNewField()));
- });
+ expectFieldChange(scope.fields.push.bind(scope.fields, getNewField()))
+ })
it(`should allow you to pop a field after initial compile`, () => {
- expectFieldChange(scope.fields.pop.bind(scope.fields));
- });
+ expectFieldChange(scope.fields.pop.bind(scope.fields))
+ })
it(`should allow you to splice out a field after initial compile`, () => {
- expectFieldChange(scope.fields.splice.bind(scope.fields, 1, 1));
- });
+ expectFieldChange(scope.fields.splice.bind(scope.fields, 1, 1))
+ })
it(`should allow you splice in a field after initial compile`, () => {
- expectFieldChange(scope.fields.splice.bind(scope.fields, 1, 0, getNewField()));
- });
+ expectFieldChange(scope.fields.splice.bind(scope.fields, 1, 0, getNewField()))
+ })
function expectTrackBy(trackBy) {
- expect(el[0].innerHTML).to.contain(`field in fields track by ${trackBy}`);
+ expect(el[0].innerHTML).to.contain(`field in fields track by ${trackBy}`)
}
function expectFieldChange(change) {
- compileAndDigest(template);
- change();
- expect(() => scope.$digest()).to.not.throw();
+ compileAndDigest(template)
+ change()
+ expect(() => scope.$digest()).to.not.throw()
}
- });
+ })
describe(`updateInitialValue`, () => {
it(`should update the initial value of the fields`, () => {
- compileAndDigest();
- const field = scope.fields[0];
- expect(field.initialValue).to.equal('myFoo');
- scope.model.foo = 'otherValue';
- scope.options.updateInitialValue();
- expect(field.initialValue).to.equal('otherValue');
- });
+ compileAndDigest()
+ const field = scope.fields[0]
+ expect(field.initialValue).to.equal('myFoo')
+ scope.model.foo = 'otherValue'
+ scope.options.updateInitialValue()
+ expect(field.initialValue).to.equal('otherValue')
+ })
it(`should reset to the updated initial value`, () => {
- compileAndDigest();
- const field = scope.fields[0];
- scope.model.foo = 'otherValue';
- scope.options.updateInitialValue();
- scope.model.foo = 'otherValueAgain';
- scope.options.resetModel();
- expect(field.initialValue).to.equal('otherValue');
- expect(scope.model.foo).to.equal('otherValue');
- });
- });
+ compileAndDigest()
+ const field = scope.fields[0]
+ scope.model.foo = 'otherValue'
+ scope.options.updateInitialValue()
+ scope.model.foo = 'otherValueAgain'
+ scope.options.resetModel()
+ expect(field.initialValue).to.equal('otherValue')
+ expect(scope.model.foo).to.equal('otherValue')
+ })
+ })
describe(`removeChromeAutoComplete`, () => {
it(`should not have a hidden input when nothing is specified`, () => {
- const el = compileAndDigest();
- const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]');
- expect(autoCompleteFixEl).to.be.null;
- });
+ const el = compileAndDigest()
+ const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]')
+ expect(autoCompleteFixEl).to.be.null
+ })
it(`should add a hidden input when specified as true`, () => {
- scope.options.removeChromeAutoComplete = true;
- const el = compileAndDigest();
- const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]');
- expect(autoCompleteFixEl).to.exist;
- });
+ scope.options.removeChromeAutoComplete = true
+ const el = compileAndDigest()
+ const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]')
+ expect(autoCompleteFixEl).to.exist
+ })
it(`should override the 'true' global configuration`, inject((formlyConfig) => {
- formlyConfig.extras.removeChromeAutoComplete = true;
- scope.options.removeChromeAutoComplete = false;
- const el = compileAndDigest();
- const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]');
- expect(autoCompleteFixEl).to.be.null;
- }));
+ formlyConfig.extras.removeChromeAutoComplete = true
+ scope.options.removeChromeAutoComplete = false
+ const el = compileAndDigest()
+ const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]')
+ expect(autoCompleteFixEl).to.be.null
+ }))
it(`should be added regardless of the option if the global config is set`, inject((formlyConfig) => {
- formlyConfig.extras.removeChromeAutoComplete = true;
- const el = compileAndDigest();
- const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]');
- expect(autoCompleteFixEl).to.exist;
- }));
- });
-
- describe(`fieldTransform`, () => {
- beforeEach(() => {
- formlyConfig.extras.fieldTransform = fieldTransform;
- });
-
- it(`should give a deprecation warning when formlyConfig.extras.fieldTransform is a function rather than an array`, inject(($log) => {
- shouldWarnWithLog(
- $log,
- [
- 'Formly Warning:',
- 'fieldTransform as a function has been deprecated.',
- /Attempted for formlyConfig.extras/
- ],
- compileAndDigest
- );
- }));
-
- it(`should give a deprecation warning when options.fieldTransform function rather than an array`, inject(($log) => {
- formlyConfig.extras.fieldTransform = undefined;
- scope.fields = [getNewField()];
- scope.options.fieldTransform = fields => fields;
- shouldWarnWithLog(
- $log,
- [
- 'Formly Warning:',
- 'fieldTransform as a function has been deprecated.',
- 'Attempted for form'
- ],
- compileAndDigest
- );
- }));
-
- it(`should throw an error if something is passed in and nothing is returned`, () => {
- scope.fields = [getNewField()];
- scope.options.fieldTransform = function() {
- // I return nothing...
- };
- expect(() => compileAndDigest()).to.throw(/^Formly Error: fieldTransform must return an array of fields/);
- });
-
- it(`should allow you to transform field configuration`, () => {
- scope.options.fieldTransform = fieldTransform;
- const spy = sinon.spy(scope.options, 'fieldTransform');
- doExpectations(spy);
- });
-
- it(`should use formlyConfig.extras.fieldTransform when not specified on options`, () => {
- const spy = sinon.spy(formlyConfig.extras, 'fieldTransform');
- doExpectations(spy);
- });
-
- it(`should allow you to use an array of transform functions`, () => {
- scope.fields = [getNewField({
- customThing: 'foo',
- otherCustomThing: {
- whatever: '|-o-|'
- }})];
- scope.options.fieldTransform = [fieldTransform];
- expect(() => compileAndDigest()).to.not.throw();
-
- const field = scope.fields[0];
- expect(field).to.have.deep.property('data.customThing');
- expect(field).to.have.deep.property('data.otherCustomThing');
- });
-
- function doExpectations(spy) {
- const originalFields = [{
- key: 'keyProp',
- template: ' ',
- customThing: 'foo',
- otherCustomThing: {
- whatever: '|-o-|'
- }
- }];
- scope.fields = originalFields;
- compileAndDigest();
- expect(spy).to.have.been.calledWith(originalFields, scope.model, scope.options, scope.form);
- const field = scope.fields[0];
-
- expect(field).to.not.have.property('customThing');
- expect(field).to.not.have.property('otherCustomThing');
- expect(field).to.have.deep.property('data.customThing');
- expect(field).to.have.deep.property('data.otherCustomThing');
- }
-
- function fieldTransform(fields) {
- const extraKeys = ['customThing', 'otherCustomThing'];
- return _.map(fields, field => {
- const newField = {data: {}};
- _.each(field, (val, name) => {
- if (_.contains(extraKeys, name)) {
- newField.data[name] = val;
- } else {
- newField[name] = val;
- }
- });
- return newField;
- });
- }
- });
+ formlyConfig.extras.removeChromeAutoComplete = true
+ const el = compileAndDigest()
+ const autoCompleteFixEl = el[0].querySelector('[autocomplete="address-level4"]')
+ expect(autoCompleteFixEl).to.exist
+ }))
+ })
describe(`data`, () => {
it(`should allow you to put whatever you want in data`, () => {
- scope.options.data = {foo: 'bar'};
- expect(compileAndDigest).to.not.throw();
- });
- });
- });
+ scope.options.data = {foo: 'bar'}
+ expect(compileAndDigest).to.not.throw()
+ })
+ })
+ })
function compileAndDigest(template) {
- el = $compile(template || basicForm)(scope);
- scope.$digest();
- return el;
+ el = $compile(template || basicForm)(scope)
+ scope.$digest()
+ return el
}
describe(`field watchers`, () => {
it('should throw for a watcher with no listener', () => {
scope.fields = [getNewField({
- watcher: {}
- })];
+ watcher: {},
+ })]
- expect(compileAndDigest).to.throw();
- });
+ expect(compileAndDigest).to.throw()
+ })
it(`should setup any watchers specified on a field`, () => {
- scope.model = {};
+ scope.model = {}
- const listener = sinon.spy();
- const expression = sinon.spy();
+ const listener = sinon.spy()
+ const expression = sinon.spy()
scope.fields = [getNewField({
watcher: {
- listener: ''
- }
+ listener: '',
+ },
}), getNewField({
watcher: [{
listener: '',
- expression: ''
+ expression: '',
}, {
listener,
- expression
+ expression,
+ }],
+ })]
+
+ expect(compileAndDigest).to.not.throw()
+ expect(listener).to.have.been.called
+ expect(expression).to.have.been.called
+ })
+
+ it(`should setup any watchers specified on a fieldgroup`, () => {
+ scope.model = {}
+
+ const listener = sinon.spy()
+ const expression = sinon.spy()
+
+ scope.fields = [{
+ watcher: [{
+ listener: '',
+ expression: '',
+ }, {
+ listener,
+ expression,
+ }],
+ fieldGroup: [
+ getNewField({}),
+ getNewField({}),
+ ],
+ }]
+
+ expect(compileAndDigest).to.not.throw()
+ expect(listener).to.have.been.called
+ expect(expression).to.have.been.called
+ })
+ })
+
+ describe(`manualModelWatcher option`, () => {
+ beforeEach(() => {
+ scope.model = {
+ foo: 'myFoo',
+ bar: 123,
+ baz: {buzz: 'myBuzz'},
+ }
+
+ scope.fields = [
+ {template: input, key: 'foo'},
+ {template: input, key: 'bar', templateOptions: {type: 'number'}},
+ ]
+ })
+
+ describe('declared as a boolean', () => {
+ beforeEach(() => {
+ scope.options = {
+ manualModelWatcher: true,
+ }
+ })
+
+ it(`should block a global model watcher`, () => {
+ const spy = sinon.spy()
+
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy.reset()
+
+ scope.model.foo = 'bar'
+
+ scope.$digest()
+ $timeout.verifyNoPendingTasks()
+
+ expect(spy).to.not.have.been.called
+ })
+
+ it(`should watch manually selected model property`, () => {
+ const spy = sinon.spy()
+
+ scope.fields[0].watcher = [{
+ expression: 'model.foo',
+ runFieldExpressions: true,
+ }]
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy.reset()
+
+ scope.model.foo = 'bar'
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(spy).to.have.been.called
+ })
+
+ it(`should not watch model properties that do not have manual watcher defined`, () => {
+ const spy = sinon.spy()
+
+ scope.fields[0].watcher = [{
+ expression: 'model.foo',
+ runFieldExpressions: true,
+ }]
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy.reset()
+
+ scope.model.bar = 123
+
+ scope.$digest()
+ $timeout.verifyNoPendingTasks()
+
+ expect(spy).to.not.have.been.called
+ })
+
+ it(`should run manual watchers defined as a function`, () => {
+ const spy = sinon.spy()
+ const stub = sinon.stub()
+
+ scope.fields[0].watcher = [{
+ expression: stub,
+ runFieldExpressions: true,
}]
- })];
-
- expect(compileAndDigest).to.not.throw();
- expect(listener).to.have.been.called;
- expect(expression).to.have.been.called;
- });
- });
-});
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ stub.reset()
+ spy.reset()
+
+ // set random stub value so it triggers watcher function
+ stub.returns(Math.random())
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(stub).to.have.been.called
+ expect(spy).to.have.been.called
+ })
+
+ it('should not trigger watches on other fields', () => {
+ const spy1 = sinon.spy()
+ const spy2 = sinon.spy()
+
+ scope.fields[0].watcher = [{
+ expression: 'model.foo',
+ runFieldExpressions: true,
+ }]
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy1,
+ }
+ scope.fields[1].expressionProperties = {
+ 'templateOptions.label': spy2,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy1.reset()
+ spy2.reset()
+
+ scope.model.foo = 'asd'
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(spy1).to.have.been.called
+ expect(spy2).to.not.have.been.called
+ })
+
+ it('works with models that are declared as string (relative model)', () => {
+ const spy = sinon.spy()
+ const model = 'model.nested'
+
+ scope.model = {
+ nested: {
+ foo: 'foo',
+ },
+ }
+ scope.fields[0].model = model
+ scope.fields[0].watcher = [{
+ expression: 'model.foo',
+ runFieldExpressions: true,
+ }]
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy.reset()
+
+ scope.model.nested.foo = 'bar'
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(spy).to.have.been.called
+ })
+ })
+
+ describe('declared as a function', () => {
+ beforeEach(() => {
+ scope.options = {
+ manualModelWatcher: () => scope.model.baz,
+ }
+ })
+
+ it('works as a form-wide watcher', () => {
+ const spy = sinon.spy()
+
+ scope.options = {
+ manualModelWatcher: () => scope.model.baz,
+ }
+
+ scope.fields[1].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy.reset()
+
+ scope.model.foo = 'random string'
+
+ scope.$digest()
+ $timeout.verifyNoPendingTasks()
+
+ expect(spy).to.not.have.been.called
+
+ spy.reset()
+
+ scope.model.baz.buzz = 'random buzz string'
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(spy).to.have.been.called
+ })
+
+ it('still fires manual field watchers', () => {
+ const spy = sinon.spy()
+
+ scope.fields[0].watcher = [{
+ expression: 'model.foo',
+ runFieldExpressions: true,
+ }]
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.label': spy,
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ spy.reset()
+
+ scope.model.foo = 'bar'
+
+ scope.$digest()
+ $timeout.flush()
+
+ expect(spy).to.have.been.called
+ })
+
+ })
+
+ describe('enabled with watchAllExpressions option', () => {
+ beforeEach(() => {
+ scope.options = {
+ manualModelWatcher: true,
+ watchAllExpressions: true,
+ }
+ })
+
+ it('watches and evaluates string template expressions', () => {
+ const field = scope.fields[0]
+
+ field.expressionProperties = {
+ 'templateOptions.label': 'model.foo',
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ scope.model.foo = 'bar'
+
+ scope.$digest()
+
+ expect(field.templateOptions.label).to.equal(scope.model.foo)
+ })
+
+ it('watches and evaluates string template expressions with custom string model', () => {
+ const field = scope.fields[0]
+
+ field.model = 'model.baz'
+ field.expressionProperties = {
+ 'templateOptions.label': 'model.buzz',
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ scope.model.baz.buzz = 'bar'
+
+ scope.$digest()
+
+ expect(field.templateOptions.label).to.equal(scope.model.baz.buzz)
+ })
+
+ it('watches and evaluates string template expressions with custom object model', () => {
+ const field = scope.fields[0]
+
+ field.model = {customFoo: 'customBar'}
+ field.expressionProperties = {
+ 'templateOptions.label': 'model.customFoo',
+ }
+
+ compileAndDigest()
+ $timeout.flush()
+
+ field.model.customFoo = 'bar'
+
+ scope.$digest()
+
+ expect(field.templateOptions.label).to.equal(field.model.customFoo)
+ })
+
+ it('watches and evaluates hideExpression', () => {
+ const field = scope.fields[0]
+
+ field.hideExpression = 'model.foo === "bar"'
+
+ compileAndDigest()
+ $timeout.flush()
+
+ scope.model.foo = 'bar'
+
+ scope.$digest()
+
+ expect(field.hide).to.equal(true)
+ })
+
+ it('watches and evaluates hideExpression with custom string model', () => {
+ const field = scope.fields[0]
+
+ field.model = 'model.baz'
+ field.hideExpression = 'model.buzz === "bar"'
+
+ compileAndDigest()
+ $timeout.flush()
+
+ scope.model.baz.buzz = 'bar'
+
+ scope.$digest()
+
+ expect(field.hide).to.equal(true)
+ })
+ })
+ })
+ describe('extras', () => {
+ describe('validateOnModelChange', () => {
+ it('should run validators after expressions are set', () => {
+ let inputs, invalidInputs, el
+
+ scope.model = {
+ foo: null,
+ bar: 123,
+ }
+
+ scope.fields = [
+ {template: input, key: 'foo', extras: {validateOnModelChange: true}},
+ {template: input, key: 'bar', templateOptions: {type: 'number'}},
+ ]
+ // First Field isn't valid when second field is 1
+ scope.fields[0].expressionProperties = {
+ 'templateOptions.isValid': 'model.bar !== 1',
+ }
+ // validator to use isValid attribute
+ scope.fields[0].validators = {isValid: {expression: (viewValue, modelValue, fieldScope) => {
+ return fieldScope.to.isValid
+ }}}
+
+ el = compileAndDigest()
+
+ // Input state before
+ inputs = el[0].querySelectorAll('input')
+ invalidInputs = el[0].querySelectorAll('input.ng-invalid')
+ expect(inputs.length).to.equal(2)
+ expect(invalidInputs.length).to.equal(0)
+
+ // Enter '1' into second field
+ angular.element(inputs[1]).val(1).triggerHandler('change')
+ $timeout.flush()
+
+ // Input state after
+ inputs = el[0].querySelectorAll('input')
+ invalidInputs = el[0].querySelectorAll('input.ng-invalid')
+ expect(inputs.length).to.equal(2)
+ expect(invalidInputs.length).to.equal(1)
+ })
+ })
+ })
+})
diff --git a/src/index.common.js b/src/index.common.js
index 99a9d32d..7d98f541 100644
--- a/src/index.common.js
+++ b/src/index.common.js
@@ -1,42 +1,44 @@
-import angular from 'angular-fix';
+import angular from 'angular-fix'
-import formlyApiCheck from './providers/formlyApiCheck';
-import formlyErrorAndWarningsUrlPrefix from './other/docsBaseUrl';
-import formlyUsability from './providers/formlyUsability';
-import formlyConfig from './providers/formlyConfig';
-import formlyValidationMessages from './providers/formlyValidationMessages';
-import formlyUtil from './services/formlyUtil';
-import formlyWarn from './services/formlyWarn';
+import formlyApiCheck from './providers/formlyApiCheck'
+import formlyErrorAndWarningsUrlPrefix from './other/docsBaseUrl'
+import formlyUsability from './providers/formlyUsability'
+import formlyConfig from './providers/formlyConfig'
+import formlyValidationMessages from './providers/formlyValidationMessages'
+import formlyUtil from './services/formlyUtil'
+import formlyWarn from './services/formlyWarn'
-import formlyCustomValidation from './directives/formly-custom-validation';
-import formlyField from './directives/formly-field';
-import formlyFocus from './directives/formly-focus';
-import formlyForm from './directives/formly-form';
+import formlyCustomValidation from './directives/formly-custom-validation'
+import formlyField from './directives/formly-field'
+import formlyFocus from './directives/formly-focus'
+import formlyForm from './directives/formly-form'
+import FormlyFormController from './directives/formly-form.controller'
-import formlyNgModelAttrsManipulator from './run/formlyNgModelAttrsManipulator';
-import formlyCustomTags from './run/formlyCustomTags';
+import formlyNgModelAttrsManipulator from './run/formlyNgModelAttrsManipulator'
+import formlyCustomTags from './run/formlyCustomTags'
-const ngModuleName = 'formly';
+const ngModuleName = 'formly'
-export default ngModuleName;
+export default ngModuleName
-const ngModule = angular.module(ngModuleName, []);
+const ngModule = angular.module(ngModuleName, [])
-ngModule.constant('formlyApiCheck', formlyApiCheck);
-ngModule.constant('formlyErrorAndWarningsUrlPrefix', formlyErrorAndWarningsUrlPrefix);
-ngModule.constant('formlyVersion', VERSION); // <-- webpack variable
+ngModule.constant('formlyApiCheck', formlyApiCheck)
+ngModule.constant('formlyErrorAndWarningsUrlPrefix', formlyErrorAndWarningsUrlPrefix)
+ngModule.constant('formlyVersion', VERSION) // <-- webpack variable
-ngModule.provider('formlyUsability', formlyUsability);
-ngModule.provider('formlyConfig', formlyConfig);
+ngModule.provider('formlyUsability', formlyUsability)
+ngModule.provider('formlyConfig', formlyConfig)
-ngModule.factory('formlyValidationMessages', formlyValidationMessages);
-ngModule.factory('formlyUtil', formlyUtil);
-ngModule.factory('formlyWarn', formlyWarn);
+ngModule.factory('formlyValidationMessages', formlyValidationMessages)
+ngModule.factory('formlyUtil', formlyUtil)
+ngModule.factory('formlyWarn', formlyWarn)
-ngModule.directive('formlyCustomValidation', formlyCustomValidation);
-ngModule.directive('formlyField', formlyField);
-ngModule.directive('formlyFocus', formlyFocus);
-ngModule.directive('formlyForm', formlyForm);
+ngModule.directive('formlyCustomValidation', formlyCustomValidation)
+ngModule.directive('formlyField', formlyField)
+ngModule.directive('formlyFocus', formlyFocus)
+ngModule.directive('formlyForm', formlyForm)
+ngModule.controller('FormlyFormController', FormlyFormController)
-ngModule.run(formlyNgModelAttrsManipulator);
-ngModule.run(formlyCustomTags);
+ngModule.run(formlyNgModelAttrsManipulator)
+ngModule.run(formlyCustomTags)
diff --git a/src/index.js b/src/index.js
index 9ce67349..063ed087 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,2 +1,2 @@
-import index from './index.common';
-export default index;
+import index from './index.common'
+export default index
diff --git a/src/index.test.js b/src/index.test.js
index 1600bb06..12e8f30f 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -1,16 +1,17 @@
// Load up the angular formly module
-import index from './index.common';
+import index from './index.common'
// Bring in the test suites
-import './providers/formlyApiCheck.test';
-import './providers/formlyConfig.test';
-import './services/formlyUtil.test';
-import './directives/formly-custom-validation.test';
-import './directives/formly-field.test';
-import './directives/formly-focus.test';
-import './directives/formly-form.test';
-import './run/formlyCustomTags.test';
-import './run/formlyNgModelAttrsManipulator.test';
-import './other/utils.test';
+import './providers/formlyApiCheck.test'
+import './providers/formlyConfig.test'
+import './services/formlyUtil.test'
+import './directives/formly-custom-validation.test'
+import './directives/formly-field.test'
+import './directives/formly-focus.test'
+import './directives/formly-form.test'
+import './directives/formly-form.controller.test'
+import './run/formlyCustomTags.test'
+import './run/formlyNgModelAttrsManipulator.test'
+import './other/utils.test'
-export default index;
+export default index
diff --git a/src/other/docsBaseUrl.js b/src/other/docsBaseUrl.js
index c75a144d..5db163ce 100644
--- a/src/other/docsBaseUrl.js
+++ b/src/other/docsBaseUrl.js
@@ -1 +1 @@
-export default `https://github.com/formly-js/angular-formly/blob/${VERSION}/other/ERRORS_AND_WARNINGS.md#`;
+export default `https://github.com/formly-js/angular-formly/blob/${VERSION}/other/ERRORS_AND_WARNINGS.md#`
diff --git a/src/other/utils.js b/src/other/utils.js
index e330d9e6..aa6f1aa4 100644
--- a/src/other/utils.js
+++ b/src/other/utils.js
@@ -1,68 +1,81 @@
-import angular from 'angular-fix';
+import angular from 'angular-fix'
export default {
- formlyEval, getFieldId, reverseDeepMerge, findByNodeName, arrayify, extendFunction, extendArray, startsWith, contains
-};
+ containsSelector, containsSpecialChar, formlyEval, getFieldId, reverseDeepMerge, findByNodeName,
+ arrayify, extendFunction, extendArray, startsWith, contains,
+}
+
+function containsSelector(string) {
+ return containsSpecialChar(string, '.') || (containsSpecialChar(string, '[') && containsSpecialChar(string, ']'))
+}
+
+function containsSpecialChar(a, b) {
+ if (!a || !a.indexOf) {
+ return false
+ }
+ return a.indexOf(b) !== -1
+}
+
function formlyEval(scope, expression, $modelValue, $viewValue, extraLocals) {
if (angular.isFunction(expression)) {
- return expression($viewValue, $modelValue, scope, extraLocals);
+ return expression($viewValue, $modelValue, scope, extraLocals)
} else {
- return scope.$eval(expression, angular.extend({$viewValue, $modelValue}, extraLocals));
+ return scope.$eval(expression, angular.extend({$viewValue, $modelValue}, extraLocals))
}
}
function getFieldId(formId, options, index) {
if (options.id) {
- return options.id;
+ return options.id
}
- let type = options.type;
+ let type = options.type
if (!type && options.template) {
- type = 'template';
+ type = 'template'
} else if (!type && options.templateUrl) {
- type = 'templateUrl';
+ type = 'templateUrl'
}
- return [formId, type, options.key, index].join('_');
+ return [formId, type, options.key, index].join('_')
}
function reverseDeepMerge(dest) {
angular.forEach(arguments, (src, index) => {
if (!index) {
- return;
+ return
}
angular.forEach(src, (val, prop) => {
if (!angular.isDefined(dest[prop])) {
- dest[prop] = angular.copy(val);
+ dest[prop] = angular.copy(val)
} else if (objAndSameType(dest[prop], val)) {
- reverseDeepMerge(dest[prop], val);
+ reverseDeepMerge(dest[prop], val)
}
- });
- });
- return dest;
+ })
+ })
+ return dest
}
function objAndSameType(obj1, obj2) {
return angular.isObject(obj1) && angular.isObject(obj2) &&
- Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2);
+ Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2)
}
// recurse down a node tree to find a node with matching nodeName, for custom tags jQuery.find doesn't work in IE8
function findByNodeName(el, nodeName) {
if (!el.prop) { // not a jQuery or jqLite object -> wrap it
- el = angular.element(el);
+ el = angular.element(el)
}
if (el.prop('nodeName') === nodeName.toUpperCase()) {
- return el;
+ return el
}
- const c = el.children();
+ const c = el.children()
for (let i = 0; c && i < c.length; i++) {
- const node = findByNodeName(c[i], nodeName);
+ const node = findByNodeName(c[i], nodeName)
if (node) {
- return node;
+ return node
}
}
}
@@ -70,52 +83,52 @@ function findByNodeName(el, nodeName) {
function arrayify(obj) {
if (obj && !angular.isArray(obj)) {
- obj = [obj];
+ obj = [obj]
} else if (!obj) {
- obj = [];
+ obj = []
}
- return obj;
+ return obj
}
function extendFunction(...fns) {
return function extendedFunction() {
- const args = arguments;
- fns.forEach(fn => fn.apply(null, args));
- };
+ const args = arguments
+ fns.forEach(fn => fn.apply(null, args))
+ }
}
function extendArray(primary, secondary, property) {
if (property) {
- primary = primary[property];
- secondary = secondary[property];
+ primary = primary[property]
+ secondary = secondary[property]
}
if (secondary && primary) {
angular.forEach(secondary, function(item) {
if (primary.indexOf(item) === -1) {
- primary.push(item);
+ primary.push(item)
}
- });
- return primary;
+ })
+ return primary
} else if (secondary) {
- return secondary;
+ return secondary
} else {
- return primary;
+ return primary
}
}
function startsWith(str, search) {
if (angular.isString(str) && angular.isString(search)) {
- return str.length >= search.length && str.substring(0, search.length) === search;
+ return str.length >= search.length && str.substring(0, search.length) === search
} else {
- return false;
+ return false
}
}
function contains(str, search) {
if (angular.isString(str) && angular.isString(search)) {
- return str.length >= search.length && str.indexOf(search) !== -1;
+ return str.length >= search.length && str.indexOf(search) !== -1
} else {
- return false;
+ return false
}
}
diff --git a/src/other/utils.test.js b/src/other/utils.test.js
index 20a78b7b..4b0b2624 100644
--- a/src/other/utils.test.js
+++ b/src/other/utils.test.js
@@ -1,44 +1,44 @@
/* eslint no-unused-vars:0 */
-import utils from './utils.js';
+import utils from './utils.js'
// gotta do this because webstorm/jshint doesn't like destructuring imports :-(
-const {extendFunction, startsWith} = utils;
+const {extendFunction, startsWith} = utils
describe(`utils`, () => {
describe(`extendFunction`, () => {
- let fn1, fn2, fn3;
+ let fn1, fn2, fn3
beforeEach(() => {
- fn1 = sinon.spy();
- fn2 = sinon.spy();
- fn3 = sinon.spy();
- });
+ fn1 = sinon.spy()
+ fn2 = sinon.spy()
+ fn3 = sinon.spy()
+ })
it(`should call all functions with the given`, () => {
- const extended = extendFunction(fn1, fn2);
- extended('foo');
+ const extended = extendFunction(fn1, fn2)
+ extended('foo')
- expect(fn1).to.have.been.calledWith('foo');
- });
- });
+ expect(fn1).to.have.been.calledWith('foo')
+ })
+ })
describe(`startsWith`, () => {
it(`should return true if a string has a given prefix`, () => {
- expect(startsWith('fooBar', 'foo')).to.be.true;
- });
+ expect(startsWith('fooBar', 'foo')).to.be.true
+ })
it(`should return false if a string does not have a given prefix`, () => {
- expect(startsWith('fooBar', 'nah')).to.be.false;
- });
+ expect(startsWith('fooBar', 'nah')).to.be.false
+ })
it(`should return false if no a string`, () => {
- expect(startsWith(undefined, 'foo')).to.be.false;
- expect(startsWith(5, 'foo')).to.be.false;
- expect(startsWith('foo', undefined)).to.be.false;
- expect(startsWith('foo', 5)).to.be.false;
- expect(startsWith(undefined, undefined)).to.be.false;
- });
- });
-
-});
+ expect(startsWith(undefined, 'foo')).to.be.false
+ expect(startsWith(5, 'foo')).to.be.false
+ expect(startsWith('foo', undefined)).to.be.false
+ expect(startsWith('foo', 5)).to.be.false
+ expect(startsWith(undefined, undefined)).to.be.false
+ })
+ })
+
+})
diff --git a/src/providers/formlyApiCheck.js b/src/providers/formlyApiCheck.js
index ecf5d98e..cb1f32b8 100644
--- a/src/providers/formlyApiCheck.js
+++ b/src/providers/formlyApiCheck.js
@@ -1,47 +1,47 @@
-import angular from 'angular-fix';
-import apiCheckFactory from 'api-check';
+import angular from 'angular-fix'
+import apiCheckFactory from 'api-check'
const apiCheck = apiCheckFactory({
output: {
prefix: 'angular-formly:',
- docsBaseUrl: require('../other/docsBaseUrl')
- }
-});
+ docsBaseUrl: require('../other/docsBaseUrl'),
+ },
+})
function shapeRequiredIfNot(otherProps, propChecker) {
if (!angular.isArray(otherProps)) {
- otherProps = [otherProps];
+ otherProps = [otherProps]
}
- const type = `specified if these are not specified: \`${otherProps.join(', ')}\` (otherwise it's optional)`;
+ const type = `specified if these are not specified: \`${otherProps.join(', ')}\` (otherwise it's optional)`
function shapeRequiredIfNotDefinition(prop, propName, location, obj) {
- const propExists = obj && obj.hasOwnProperty(propName);
+ const propExists = obj && obj.hasOwnProperty(propName)
const otherPropsExist = otherProps.some(function(otherProp) {
- return obj && obj.hasOwnProperty(otherProp);
- });
+ return obj && obj.hasOwnProperty(otherProp)
+ })
if (!otherPropsExist && !propExists) {
- return apiCheck.utils.getError(propName, location, type);
+ return apiCheck.utils.getError(propName, location, type)
} else if (propExists) {
- return propChecker(prop, propName, location, obj);
+ return propChecker(prop, propName, location, obj)
}
}
- shapeRequiredIfNotDefinition.type = type;
- return apiCheck.utils.checkerHelpers.setupChecker(shapeRequiredIfNotDefinition);
+ shapeRequiredIfNotDefinition.type = type
+ return apiCheck.utils.checkerHelpers.setupChecker(shapeRequiredIfNotDefinition)
}
-const formlyExpression = apiCheck.oneOfType([apiCheck.string, apiCheck.func]);
-const specifyWrapperType = apiCheck.typeOrArrayOf(apiCheck.string).nullable;
+const formlyExpression = apiCheck.oneOfType([apiCheck.string, apiCheck.func])
+const specifyWrapperType = apiCheck.typeOrArrayOf(apiCheck.string).nullable
-const apiCheckProperty = apiCheck.func;
+const apiCheckProperty = apiCheck.func
const apiCheckInstanceProperty = apiCheck.shape.onlyIf('apiCheck', apiCheck.func.withProperties({
warn: apiCheck.func,
throw: apiCheck.func,
- shape: apiCheck.func
-}));
+ shape: apiCheck.func,
+}))
-const apiCheckFunctionProperty = apiCheck.shape.onlyIf('apiCheck', apiCheck.oneOf(['throw', 'warn']));
+const apiCheckFunctionProperty = apiCheck.shape.onlyIf('apiCheck', apiCheck.oneOf(['throw', 'warn']))
const formlyWrapperType = apiCheck.shape({
name: shapeRequiredIfNot('types', apiCheck.string).optional,
@@ -52,30 +52,38 @@ const formlyWrapperType = apiCheck.shape({
apiCheck: apiCheckProperty.optional,
apiCheckInstance: apiCheckInstanceProperty.optional,
apiCheckFunction: apiCheckFunctionProperty.optional,
- apiCheckOptions: apiCheck.object.optional
-}).strict;
+ apiCheckOptions: apiCheck.object.optional,
+}).strict
const expressionProperties = apiCheck.objectOf(apiCheck.oneOfType([
formlyExpression,
apiCheck.shape({
expression: formlyExpression,
- message: formlyExpression.optional
- }).strict
-]));
+ message: formlyExpression.optional,
+ }).strict,
+]))
-const modelChecker = apiCheck.oneOfType([apiCheck.string, apiCheck.object]);
+const modelChecker = apiCheck.oneOfType([apiCheck.string, apiCheck.object])
const templateManipulators = apiCheck.shape({
preWrapper: apiCheck.arrayOf(apiCheck.func).nullable.optional,
- postWrapper: apiCheck.arrayOf(apiCheck.func).nullable.optional
-}).strict.nullable;
+ postWrapper: apiCheck.arrayOf(apiCheck.func).nullable.optional,
+}).strict.nullable
const validatorChecker = apiCheck.objectOf(apiCheck.oneOfType([
formlyExpression, apiCheck.shape({
expression: formlyExpression,
- message: formlyExpression.optional
- }).strict
-]));
+ message: formlyExpression.optional,
+ }).strict,
+]))
+
+const watcherChecker = apiCheck.typeOrArrayOf(
+ apiCheck.shape({
+ expression: formlyExpression.optional,
+ listener: formlyExpression.optional,
+ runFieldExpressions: apiCheck.bool.optional,
+ })
+)
const fieldOptionsApiShape = {
$$hashKey: apiCheck.any.optional,
@@ -98,8 +106,8 @@ const fieldOptionsApiShape = {
extras: apiCheck.shape({
validateOnModelChange: apiCheck.bool.optional,
skipNgModelAttrsManipulator: apiCheck.oneOfType([
- apiCheck.string, apiCheck.bool
- ]).optional
+ apiCheck.string, apiCheck.bool,
+ ]).optional,
}).strict.optional,
data: apiCheck.object.optional,
templateOptions: apiCheck.object.optional,
@@ -107,18 +115,13 @@ const fieldOptionsApiShape = {
modelOptions: apiCheck.shape({
updateOn: apiCheck.string.optional,
debounce: apiCheck.oneOfType([
- apiCheck.objectOf(apiCheck.number), apiCheck.number
+ apiCheck.objectOf(apiCheck.number), apiCheck.number,
]).optional,
allowInvalid: apiCheck.bool.optional,
getterSetter: apiCheck.bool.optional,
- timezone: apiCheck.string.optional
+ timezone: apiCheck.string.optional,
}).optional,
- watcher: apiCheck.typeOrArrayOf(
- apiCheck.shape({
- expression: formlyExpression.optional,
- listener: formlyExpression
- })
- ).optional,
+ watcher: watcherChecker.optional,
validators: validatorChecker.optional,
asyncValidators: validatorChecker.optional,
parsers: apiCheck.arrayOf(formlyExpression).optional,
@@ -132,18 +135,18 @@ const fieldOptionsApiShape = {
value: apiCheck.shape.ifNot('statement', apiCheck.any).optional,
attribute: apiCheck.shape.ifNot('statement', apiCheck.any).optional,
bound: apiCheck.shape.ifNot('statement', apiCheck.any).optional,
- boolean: apiCheck.shape.ifNot('statement', apiCheck.any).optional
+ boolean: apiCheck.shape.ifNot('statement', apiCheck.any).optional,
}).strict).optional,
elementAttributes: apiCheck.objectOf(apiCheck.string).optional,
optionsTypes: apiCheck.typeOrArrayOf(apiCheck.string).optional,
link: apiCheck.func.optional,
controller: apiCheck.oneOfType([
- apiCheck.string, apiCheck.func, apiCheck.array
+ apiCheck.string, apiCheck.func, apiCheck.array,
]).optional,
validation: apiCheck.shape({
show: apiCheck.bool.nullable.optional,
messages: apiCheck.objectOf(formlyExpression).optional,
- errorExistsAndShouldBeVisible: apiCheck.bool.optional
+ errorExistsAndShouldBeVisible: apiCheck.bool.optional,
}).optional,
formControl: apiCheck.typeOrArrayOf(apiCheck.object).optional,
value: apiCheck.func.optional,
@@ -152,24 +155,27 @@ const fieldOptionsApiShape = {
resetModel: apiCheck.func.optional,
updateInitialValue: apiCheck.func.optional,
initialValue: apiCheck.any.optional,
- defaultValue: apiCheck.any.optional
-};
+ defaultValue: apiCheck.any.optional,
+}
-const formlyFieldOptions = apiCheck.shape(fieldOptionsApiShape).strict;
+const formlyFieldOptions = apiCheck.shape(fieldOptionsApiShape).strict
const formOptionsApi = apiCheck.shape({
formState: apiCheck.object.optional,
resetModel: apiCheck.func.optional,
updateInitialValue: apiCheck.func.optional,
removeChromeAutoComplete: apiCheck.bool.optional,
+ parseKeyArrays: apiCheck.bool.optional,
templateManipulators: templateManipulators.optional,
+ manualModelWatcher: apiCheck.oneOfType([apiCheck.bool, apiCheck.func]).optional,
+ watchAllExpressions: apiCheck.bool.optional,
wrapper: specifyWrapperType.optional,
fieldTransform: apiCheck.oneOfType([
- apiCheck.func, apiCheck.array
+ apiCheck.func, apiCheck.array,
]).optional,
- data: apiCheck.object.optional
-}).strict;
+ data: apiCheck.object.optional,
+}).strict
const fieldGroup = apiCheck.shape({
@@ -179,27 +185,30 @@ const fieldGroup = apiCheck.shape({
fieldGroup: apiCheck.arrayOf(apiCheck.oneOfType([formlyFieldOptions, apiCheck.object])),
className: apiCheck.string.optional,
options: formOptionsApi.optional,
+ templateOptions: apiCheck.object.optional,
+ wrapper: specifyWrapperType.optional,
+ watcher: watcherChecker.optional,
hide: apiCheck.bool.optional,
hideExpression: formlyExpression.optional,
data: apiCheck.object.optional,
model: modelChecker.optional,
form: apiCheck.object.optional,
- elementAttributes: apiCheck.objectOf(apiCheck.string).optional
-}).strict;
+ elementAttributes: apiCheck.objectOf(apiCheck.string).optional,
+}).strict
-const typeOptionsDefaultOptions = angular.copy(fieldOptionsApiShape);
-typeOptionsDefaultOptions.key = apiCheck.string.optional;
+const typeOptionsDefaultOptions = angular.copy(fieldOptionsApiShape)
+typeOptionsDefaultOptions.key = apiCheck.string.optional
const formlyTypeOptions = apiCheck.shape({
name: apiCheck.string,
template: apiCheck.shape.ifNot('templateUrl', apiCheck.oneOfType([apiCheck.string, apiCheck.func])).optional,
templateUrl: apiCheck.shape.ifNot('template', apiCheck.oneOfType([apiCheck.string, apiCheck.func])).optional,
controller: apiCheck.oneOfType([
- apiCheck.func, apiCheck.string, apiCheck.array
+ apiCheck.func, apiCheck.string, apiCheck.array,
]).optional,
link: apiCheck.func.optional,
defaultOptions: apiCheck.oneOfType([
- apiCheck.func, apiCheck.shape(typeOptionsDefaultOptions)
+ apiCheck.func, apiCheck.shape(typeOptionsDefaultOptions),
]).optional,
extends: apiCheck.string.optional,
wrapper: specifyWrapperType.optional,
@@ -208,11 +217,11 @@ const formlyTypeOptions = apiCheck.shape({
apiCheckInstance: apiCheckInstanceProperty.optional,
apiCheckFunction: apiCheckFunctionProperty.optional,
apiCheckOptions: apiCheck.object.optional,
- overwriteOk: apiCheck.bool.optional
-}).strict;
+ overwriteOk: apiCheck.bool.optional,
+}).strict
angular.extend(apiCheck, {
- formlyTypeOptions, formlyFieldOptions, formlyExpression, formlyWrapperType, fieldGroup, formOptionsApi
-});
+ formlyTypeOptions, formlyFieldOptions, formlyExpression, formlyWrapperType, fieldGroup, formOptionsApi,
+})
-export default apiCheck;
+export default apiCheck
diff --git a/src/providers/formlyApiCheck.test.js b/src/providers/formlyApiCheck.test.js
index 13bd1552..ac7aff7b 100644
--- a/src/providers/formlyApiCheck.test.js
+++ b/src/providers/formlyApiCheck.test.js
@@ -1,13 +1,13 @@
/* jshint maxlen:false */
describe('formlyApiCheck', () => {
- beforeEach(window.module('formly'));
+ beforeEach(window.module('formly'))
- let formlyApiCheck;
+ let formlyApiCheck
beforeEach(inject((_formlyApiCheck_) => {
- formlyApiCheck = _formlyApiCheck_;
- }));
+ formlyApiCheck = _formlyApiCheck_
+ }))
describe('formlyFieldOptions', () => {
it(`should pass when validation.messages is an object of functions or strings`, () => {
@@ -18,19 +18,19 @@ describe('formlyApiCheck', () => {
messages: {
thing1() {
},
- thing2: '"Formly Expression"'
- }
- }
- }, 'formlyFieldOptions');
- });
+ thing2: '"Formly Expression"',
+ },
+ },
+ }, 'formlyFieldOptions')
+ })
it(`should allow $$hashKey`, () => {
expectPass({
$$hashKey: 'object:1',
template: 'hello',
- key: 'whatevs'
- }, 'formlyFieldOptions');
- });
+ key: 'whatevs',
+ }, 'formlyFieldOptions')
+ })
describe('ngModelAttrs', () => {
it(`should allow property of 'boolean'`, () => {
@@ -38,67 +38,67 @@ describe('formlyApiCheck', () => {
template: 'hello',
key: 'whatevs',
templateOptions: {
- foo: 'bar'
+ foo: 'bar',
},
ngModelAttrs: {
foo: {
- boolean: 'foo-bar'
- }
- }
- }, 'formlyFieldOptions');
- });
- });
- });
+ boolean: 'foo-bar',
+ },
+ },
+ }, 'formlyFieldOptions')
+ })
+ })
+ })
describe(`fieldGroup`, () => {
it(`should pass when specifying data`, () => {
expectPass({
fieldGroup: [],
- data: {foo: 'bar'}
- }, 'fieldGroup');
- });
- });
+ data: {foo: 'bar'},
+ }, 'fieldGroup')
+ })
+ })
describe(`extras`, () => {
describe(`skipNgModelAttrsManipulator`, () => {
it(`should pass with a boolean`, () => {
expectPass({
template: 'foo',
- extras: {skipNgModelAttrsManipulator: true}
- }, 'formlyFieldOptions');
- });
+ extras: {skipNgModelAttrsManipulator: true},
+ }, 'formlyFieldOptions')
+ })
it(`should pass with a string`, () => {
expectPass({
template: 'foo',
- extras: {skipNgModelAttrsManipulator: '.selector'}
- }, 'formlyFieldOptions');
- });
+ extras: {skipNgModelAttrsManipulator: '.selector'},
+ }, 'formlyFieldOptions')
+ })
it(`should pass with nothing`, () => {
expectPass({
template: 'foo',
- extras: {skipNgModelAttrsManipulator: '.selector'}
- }, 'formlyFieldOptions');
- });
+ extras: {skipNgModelAttrsManipulator: '.selector'},
+ }, 'formlyFieldOptions')
+ })
it(`should fail with anything else`, () => {
expectFail({
template: 'foo',
- extras: {skipNgModelAttrsManipulator: 32}
- }, 'formlyFieldOptions');
- });
- });
- });
+ extras: {skipNgModelAttrsManipulator: 32},
+ }, 'formlyFieldOptions')
+ })
+ })
+ })
function expectPass(options, checker) {
- const result = formlyApiCheck[checker](options);
- expect(result).to.be.undefined;
+ const result = formlyApiCheck[checker](options)
+ expect(result).to.be.undefined
}
function expectFail(options, checker) {
- const result = formlyApiCheck[checker](options);
- expect(result).to.be.an.instanceOf(Error);
+ const result = formlyApiCheck[checker](options)
+ expect(result).to.be.an.instanceOf(Error)
}
-});
+})
diff --git a/src/providers/formlyConfig.js b/src/providers/formlyConfig.js
index 2035f4da..8737c5d3 100644
--- a/src/providers/formlyConfig.js
+++ b/src/providers/formlyConfig.js
@@ -1,20 +1,21 @@
-import angular from 'angular-fix';
-import utils from '../other/utils';
+import angular from 'angular-fix'
+import utils from '../other/utils'
-export default formlyConfig;
+export default formlyConfig
// @ngInject
function formlyConfig(formlyUsabilityProvider, formlyErrorAndWarningsUrlPrefix, formlyApiCheck) {
- const typeMap = {};
- const templateWrappersMap = {};
- const defaultWrapperName = 'default';
- const _this = this;
- const getError = formlyUsabilityProvider.getFormlyError;
+ const typeMap = {}
+ const templateWrappersMap = {}
+ const defaultWrapperName = 'default'
+ const _this = this
+ const getError = formlyUsabilityProvider.getFormlyError
angular.extend(this, {
setType,
getType,
+ getTypes,
getTypeHeritage,
setWrapper,
getWrapper,
@@ -24,208 +25,213 @@ function formlyConfig(formlyUsabilityProvider, formlyErrorAndWarningsUrlPrefix,
disableWarnings: false,
extras: {
disableNgModelAttrsManipulator: false,
+ fieldTransform: [],
ngModelAttrsManipulatorPreferUnbound: false,
removeChromeAutoComplete: false,
+ parseKeyArrays: false,
defaultHideDirective: 'ng-if',
getFieldId: null,
- explicitAsync: false
},
templateManipulators: {
preWrapper: [],
- postWrapper: []
+ postWrapper: [],
},
- $get: () => this
- });
+ $get: () => this,
+ })
function setType(options) {
if (angular.isArray(options)) {
- const allTypes = [];
+ const allTypes = []
angular.forEach(options, item => {
- allTypes.push(setType(item));
- });
- return allTypes;
+ allTypes.push(setType(item))
+ })
+ return allTypes
} else if (angular.isObject(options)) {
- checkType(options);
+ checkType(options)
if (options.extends) {
- extendTypeOptions(options);
+ extendTypeOptions(options)
}
- typeMap[options.name] = options;
- return typeMap[options.name];
+ typeMap[options.name] = options
+ return typeMap[options.name]
} else {
- throw getError(`You must provide an object or array for setType. You provided: ${JSON.stringify(arguments)}`);
+ throw getError(`You must provide an object or array for setType. You provided: ${JSON.stringify(arguments)}`)
}
}
function checkType(options) {
formlyApiCheck.throw(formlyApiCheck.formlyTypeOptions, options, {
prefix: 'formlyConfig.setType',
- url: 'settype-validation-failed'
- });
+ url: 'settype-validation-failed',
+ })
if (!options.overwriteOk) {
- checkOverwrite(options.name, typeMap, options, 'types');
+ checkOverwrite(options.name, typeMap, options, 'types')
} else {
- options.overwriteOk = undefined;
+ options.overwriteOk = undefined
}
}
function extendTypeOptions(options) {
- const extendsType = getType(options.extends, true, options);
- extendTypeControllerFunction(options, extendsType);
- extendTypeLinkFunction(options, extendsType);
- extendTypeDefaultOptions(options, extendsType);
- utils.reverseDeepMerge(options, extendsType);
- extendTemplate(options, extendsType);
+ const extendsType = getType(options.extends, true, options)
+ extendTypeControllerFunction(options, extendsType)
+ extendTypeLinkFunction(options, extendsType)
+ extendTypeDefaultOptions(options, extendsType)
+ utils.reverseDeepMerge(options, extendsType)
+ extendTemplate(options, extendsType)
}
function extendTemplate(options, extendsType) {
if (options.template && extendsType.templateUrl) {
- delete options.templateUrl;
+ delete options.templateUrl
} else if (options.templateUrl && extendsType.template) {
- delete options.template;
+ delete options.template
}
}
function extendTypeControllerFunction(options, extendsType) {
- const extendsCtrl = extendsType.controller;
+ const extendsCtrl = extendsType.controller
if (!angular.isDefined(extendsCtrl)) {
- return;
+ return
}
- const optionsCtrl = options.controller;
+ const optionsCtrl = options.controller
if (angular.isDefined(optionsCtrl)) {
options.controller = function($scope, $controller) {
- $controller(extendsCtrl, {$scope});
- $controller(optionsCtrl, {$scope});
- };
- options.controller.$inject = ['$scope', '$controller'];
+ $controller(extendsCtrl, {$scope})
+ $controller(optionsCtrl, {$scope})
+ }
+ options.controller.$inject = ['$scope', '$controller']
} else {
- options.controller = extendsCtrl;
+ options.controller = extendsCtrl
}
}
function extendTypeLinkFunction(options, extendsType) {
- const extendsFn = extendsType.link;
+ const extendsFn = extendsType.link
if (!angular.isDefined(extendsFn)) {
- return;
+ return
}
- const optionsFn = options.link;
+ const optionsFn = options.link
if (angular.isDefined(optionsFn)) {
options.link = function() {
- extendsFn(...arguments);
- optionsFn(...arguments);
- };
+ extendsFn(...arguments)
+ optionsFn(...arguments)
+ }
} else {
- options.link = extendsFn;
+ options.link = extendsFn
}
}
function extendTypeDefaultOptions(options, extendsType) {
- const extendsDO = extendsType.defaultOptions;
+ const extendsDO = extendsType.defaultOptions
if (!angular.isDefined(extendsDO)) {
- return;
+ return
}
- const optionsDO = options.defaultOptions;
- const optionsDOIsFn = angular.isFunction(optionsDO);
- const extendsDOIsFn = angular.isFunction(extendsDO);
+ const optionsDO = options.defaultOptions || {}
+ const optionsDOIsFn = angular.isFunction(optionsDO)
+ const extendsDOIsFn = angular.isFunction(extendsDO)
if (extendsDOIsFn) {
options.defaultOptions = function defaultOptions(opts, scope) {
- const extendsDefaultOptions = extendsDO(opts, scope);
- const mergedDefaultOptions = {};
- utils.reverseDeepMerge(mergedDefaultOptions, opts, extendsDefaultOptions);
- let extenderOptionsDefaultOptions = optionsDO;
+ const extendsDefaultOptions = extendsDO(opts, scope)
+ const mergedDefaultOptions = {}
+ utils.reverseDeepMerge(mergedDefaultOptions, opts, extendsDefaultOptions)
+ let extenderOptionsDefaultOptions = optionsDO
if (optionsDOIsFn) {
- extenderOptionsDefaultOptions = extenderOptionsDefaultOptions(mergedDefaultOptions, scope);
+ extenderOptionsDefaultOptions = extenderOptionsDefaultOptions(mergedDefaultOptions, scope)
}
- utils.reverseDeepMerge(extendsDefaultOptions, extenderOptionsDefaultOptions);
- return extendsDefaultOptions;
- };
+ utils.reverseDeepMerge(extenderOptionsDefaultOptions, extendsDefaultOptions)
+ return extenderOptionsDefaultOptions
+ }
} else if (optionsDOIsFn) {
options.defaultOptions = function defaultOptions(opts, scope) {
- const newDefaultOptions = {};
- utils.reverseDeepMerge(newDefaultOptions, opts, extendsDO);
- return optionsDO(newDefaultOptions, scope);
- };
+ const newDefaultOptions = {}
+ utils.reverseDeepMerge(newDefaultOptions, opts, extendsDO)
+ return optionsDO(newDefaultOptions, scope)
+ }
}
}
function getType(name, throwError, errorContext) {
if (!name) {
- return undefined;
+ return undefined
}
- const type = typeMap[name];
+ const type = typeMap[name]
if (!type && throwError === true) {
throw getError(
`There is no type by the name of "${name}": ${JSON.stringify(errorContext)}`
- );
+ )
} else {
- return type;
+ return type
}
}
+ function getTypes() {
+ return typeMap
+ }
+
function getTypeHeritage(parent) {
- const heritage = [];
- let type = parent;
+ const heritage = []
+ let type = parent
if (angular.isString(type)) {
- type = getType(parent);
+ type = getType(parent)
}
- parent = type.extends;
+ parent = type.extends
while (parent) {
- type = getType(parent);
- heritage.push(type);
- parent = type.extends;
+ type = getType(parent)
+ heritage.push(type)
+ parent = type.extends
}
- return heritage;
+ return heritage
}
function setWrapper(options, name) {
if (angular.isArray(options)) {
- return options.map(wrapperOptions => setWrapper(wrapperOptions));
+ return options.map(wrapperOptions => setWrapper(wrapperOptions))
} else if (angular.isObject(options)) {
- options.types = getOptionsTypes(options);
- options.name = getOptionsName(options, name);
- checkWrapperAPI(options);
- templateWrappersMap[options.name] = options;
- return options;
+ options.types = getOptionsTypes(options)
+ options.name = getOptionsName(options, name)
+ checkWrapperAPI(options)
+ templateWrappersMap[options.name] = options
+ return options
} else if (angular.isString(options)) {
return setWrapper({
template: options,
- name
- });
+ name,
+ })
}
}
function getOptionsTypes(options) {
if (angular.isString(options.types)) {
- return [options.types];
+ return [options.types]
}
if (!angular.isDefined(options.types)) {
- return [];
+ return []
} else {
- return options.types;
+ return options.types
}
}
function getOptionsName(options, name) {
- return options.name || name || options.types.join(' ') || defaultWrapperName;
+ return options.name || name || options.types.join(' ') || defaultWrapperName
}
function checkWrapperAPI(options) {
- formlyUsabilityProvider.checkWrapper(options);
+ formlyUsabilityProvider.checkWrapper(options)
if (options.template) {
- formlyUsabilityProvider.checkWrapperTemplate(options.template, options);
+ formlyUsabilityProvider.checkWrapperTemplate(options.template, options)
}
if (!options.overwriteOk) {
- checkOverwrite(options.name, templateWrappersMap, options, 'templateWrappers');
+ checkOverwrite(options.name, templateWrappersMap, options, 'templateWrappers')
} else {
- delete options.overwriteOk;
+ delete options.overwriteOk
}
- checkWrapperTypes(options);
+ checkWrapperTypes(options)
}
function checkWrapperTypes(options) {
- const shouldThrow = !angular.isArray(options.types) || !options.types.every(angular.isString);
+ const shouldThrow = !angular.isArray(options.types) || !options.types.every(angular.isString)
if (shouldThrow) {
- throw getError(`Attempted to create a template wrapper with types that is not a string or an array of strings`);
+ throw getError(`Attempted to create a template wrapper with types that is not a string or an array of strings`)
}
}
@@ -234,44 +240,44 @@ function formlyConfig(formlyUsabilityProvider, formlyErrorAndWarningsUrlPrefix,
warn('overwriting-types-or-wrappers', [
`Attempting to overwrite ${property} on ${objectName} which is currently`,
`${JSON.stringify(object[property])} with ${JSON.stringify(newValue)}`,
- `To supress this warning, specify the property "overwriteOk: true"`
- ].join(' '));
+ `To supress this warning, specify the property "overwriteOk: true"`,
+ ].join(' '))
}
}
function getWrapper(name) {
- return templateWrappersMap[name || defaultWrapperName];
+ return templateWrappersMap[name || defaultWrapperName]
}
function getWrapperByType(type) {
/* eslint prefer-const:0 */
- const wrappers = [];
+ const wrappers = []
for (let name in templateWrappersMap) {
if (templateWrappersMap.hasOwnProperty(name)) {
if (templateWrappersMap[name].types && templateWrappersMap[name].types.indexOf(type) !== -1) {
- wrappers.push(templateWrappersMap[name]);
+ wrappers.push(templateWrappersMap[name])
}
}
}
- return wrappers;
+ return wrappers
}
function removeWrapperByName(name) {
- const wrapper = templateWrappersMap[name];
- delete templateWrappersMap[name];
- return wrapper;
+ const wrapper = templateWrappersMap[name]
+ delete templateWrappersMap[name]
+ return wrapper
}
function removeWrappersForType(type) {
- const wrappers = getWrapperByType(type);
+ const wrappers = getWrapperByType(type)
if (!wrappers) {
- return undefined;
+ return undefined
}
if (!angular.isArray(wrappers)) {
- return removeWrapperByName(wrappers.name);
+ return removeWrapperByName(wrappers.name)
} else {
- wrappers.forEach((wrapper) => removeWrapperByName(wrapper.name));
- return wrappers;
+ wrappers.forEach((wrapper) => removeWrapperByName(wrapper.name))
+ return wrappers
}
}
@@ -279,11 +285,11 @@ function formlyConfig(formlyUsabilityProvider, formlyErrorAndWarningsUrlPrefix,
function warn() {
if (!_this.disableWarnings && console.warn) {
/* eslint no-console:0 */
- const args = Array.prototype.slice.call(arguments);
- const warnInfoSlug = args.shift();
- args.unshift('Formly Warning:');
- args.push(`${formlyErrorAndWarningsUrlPrefix}${warnInfoSlug}`);
- console.warn(...args);
+ const args = Array.prototype.slice.call(arguments)
+ const warnInfoSlug = args.shift()
+ args.unshift('Formly Warning:')
+ args.push(`${formlyErrorAndWarningsUrlPrefix}${warnInfoSlug}`)
+ console.warn(...args)
}
}
}
diff --git a/src/providers/formlyConfig.test.js b/src/providers/formlyConfig.test.js
index 27025956..988a050e 100644
--- a/src/providers/formlyConfig.test.js
+++ b/src/providers/formlyConfig.test.js
@@ -3,229 +3,238 @@
/* eslint no-shadow:0 */
/* eslint no-console:0 */
/* eslint no-unused-vars:0 */
-import angular from 'angular-fix';
-import testUtils from '../test.utils.js';
+import angular from 'angular-fix'
+import testUtils from '../test.utils.js'
-const {getNewField, basicForm, shouldWarn, shouldNotWarn} = testUtils;
+const {getNewField, basicForm, shouldWarn, shouldNotWarn} = testUtils
describe('formlyConfig', () => {
- beforeEach(window.module('formly'));
+ beforeEach(window.module('formly'))
- let formlyConfig;
+ let formlyConfig
beforeEach(inject((_formlyConfig_) => {
- formlyConfig = _formlyConfig_;
- }));
+ formlyConfig = _formlyConfig_
+ }))
describe('setWrapper/getWrapper', () => {
- let getterFn, setterFn, $log;
- const template = 'This is my template';
- const templateUrl = '/path/to/my/template.html';
- const typesString = 'checkbox';
- const types = ['text', 'textarea'];
- const name = 'hi';
- const name2 = 'name2';
- const template2 = template + '2';
+ let getterFn, setterFn, $log
+ const template = 'This is my template'
+ const templateUrl = '/path/to/my/template.html'
+ const typesString = 'checkbox'
+ const types = ['text', 'textarea']
+ const name = 'hi'
+ const name2 = 'name2'
+ const template2 = template + '2'
beforeEach(inject(function(_$log_) {
- getterFn = formlyConfig.getWrapper;
- setterFn = formlyConfig.setWrapper;
- $log = _$log_;
- }));
+ getterFn = formlyConfig.getWrapper
+ setterFn = formlyConfig.setWrapper
+ $log = _$log_
+ }))
describe('\(^O^)/ path', () => {
describe('the default template', () => {
it('can be a string without a name', () => {
- setterFn(template);
- expect(getterFn()).to.eql({name: 'default', template, types: []});
- });
+ setterFn(template)
+ expect(getterFn()).to.eql({name: 'default', template, types: []})
+ })
it('can be a string with a name', () => {
- setterFn(template, name);
- expect(getterFn(name)).to.eql({name, template, types: []});
- });
+ setterFn(template, name)
+ expect(getterFn(name)).to.eql({name, template, types: []})
+ })
it('can be an object with a template', () => {
- setterFn({template});
- expect(getterFn()).to.eql({name: 'default', template, types: []});
- });
+ setterFn({template})
+ expect(getterFn()).to.eql({name: 'default', template, types: []})
+ })
it('can be an object with a template and a name', () => {
- setterFn({template, name});
- expect(getterFn(name)).to.eql({name, template, types: []});
- });
+ setterFn({template, name})
+ expect(getterFn(name)).to.eql({name, template, types: []})
+ })
it('can be an object with a templateUrl', () => {
- setterFn({templateUrl});
- expect(getterFn()).to.eql({name: 'default', templateUrl, types: []});
- });
+ setterFn({templateUrl})
+ expect(getterFn()).to.eql({name: 'default', templateUrl, types: []})
+ })
it('can be an object with a templateUrl and a name', () => {
- setterFn({name, templateUrl});
- expect(getterFn(name)).to.eql({name, templateUrl, types: []});
- });
+ setterFn({name, templateUrl})
+ expect(getterFn(name)).to.eql({name, templateUrl, types: []})
+ })
it('can be an array of objects with names, urls, and/or templates', () => {
setterFn([
{templateUrl},
{name, template},
- {name: name2, template: template2}
- ]);
- expect(getterFn()).to.eql({templateUrl, name: 'default', types: []});
- expect(getterFn(name)).to.eql({template, name, types: []});
- expect(getterFn(name2)).to.eql({template: template2, name: name2, types: []});
- });
+ {name: name2, template: template2},
+ ])
+ expect(getterFn()).to.eql({templateUrl, name: 'default', types: []})
+ expect(getterFn(name)).to.eql({template, name, types: []})
+ expect(getterFn(name2)).to.eql({template: template2, name: name2, types: []})
+ })
it('can specify types as a string (using types as the name when not specified)', () => {
- setterFn({types: typesString, template});
- expect(getterFn(typesString)).to.eql({template, name: typesString, types: [typesString]});
- });
+ setterFn({types: typesString, template})
+ expect(getterFn(typesString)).to.eql({template, name: typesString, types: [typesString]})
+ })
it('can specify types as an array (using types as the name when not specified)', () => {
- setterFn({types, template});
- expect(getterFn(types.join(' '))).to.eql({template, name: types.join(' '), types});
- });
- });
- });
+ setterFn({types, template})
+ expect(getterFn(types.join(' '))).to.eql({template, name: types.join(' '), types})
+ })
+ })
+ })
describe('(◞‸◟;) path', () => {
it('should throw an error when providing both a template and templateUrl', () => {
- expect(() => setterFn({template, templateUrl}, name)).to.throw(/`template` must be `ifNot\[templateUrl]`/i);
- });
+ expect(() => setterFn({template, templateUrl}, name)).to.throw(/`template` must be `ifNot\[templateUrl]`/i)
+ })
it('should throw an error when the template does not use formly-transclude', () => {
- const error = /templates.*?must.*?<\/formly-transclude>/;
- expect(() => setterFn({template: 'no formly-transclude'})).to.throw(error);
- });
+ const error = /templates.*?must.*?<\/formly-transclude>/
+ expect(() => setterFn({template: 'no formly-transclude'})).to.throw(error)
+ })
it('should throw an error when specifying an array type where not all items are strings', () => {
- const error = /types.*?typeOrArrayOf.*?String.*?/i;
- expect(() => setterFn({template, types: ['hi', 2, false, 'cool']})).to.throw(error);
- });
+ const error = /types.*?typeOrArrayOf.*?String.*?/i
+ expect(() => setterFn({template, types: ['hi', 2, false, 'cool']})).to.throw(error)
+ })
it('should warn when attempting to override a template wrapper', () => {
shouldWarn(/overwrite/, function() {
- setterFn({template});
- setterFn({template});
- });
- });
+ setterFn({template})
+ setterFn({template})
+ })
+ })
it('should not warn when attempting to override a template wrapper if overwriteOk is true', () => {
shouldNotWarn(() => {
- setterFn({template});
- setterFn({template, overwriteOk: true});
- });
- });
- });
+ setterFn({template})
+ setterFn({template, overwriteOk: true})
+ })
+ })
+ })
describe(`apiCheck`, () => {
- testApiCheck('setWrapper', 'getWrapper');
- });
+ testApiCheck('setWrapper', 'getWrapper')
+ })
- });
+ })
describe('getWrapperByType', () => {
- let getterFn, setterFn;
- const types = ['input', 'checkbox'];
- const types2 = ['input', 'select'];
- const templateUrl = '/path/to/file.html';
+ let getterFn, setterFn
+ const types = ['input', 'checkbox']
+ const types2 = ['input', 'select']
+ const templateUrl = '/path/to/file.html'
beforeEach(inject(function(formlyConfig) {
- setterFn = formlyConfig.setWrapper;
- getterFn = formlyConfig.getWrapperByType;
- }));
+ setterFn = formlyConfig.setWrapper
+ getterFn = formlyConfig.getWrapperByType
+ }))
describe('\(^O^)/ path', () => {
it('should return a template wrapper that has the same type', () => {
- const option = setterFn({templateUrl, types});
- expect(getterFn(types[0])).to.eql([option]);
- });
+ const option = setterFn({templateUrl, types})
+ expect(getterFn(types[0])).to.eql([option])
+ })
it('should return an array when multiple wrappers have the same time', () => {
- setterFn({templateUrl, types});
- setterFn({templateUrl, types: types2});
- const inputWrappers = getterFn('input');
- expect(inputWrappers).to.be.instanceOf(Array);
- expect(inputWrappers).to.have.length(2);
- });
+ setterFn({templateUrl, types})
+ setterFn({templateUrl, types: types2})
+ const inputWrappers = getterFn('input')
+ expect(inputWrappers).to.be.instanceOf(Array)
+ expect(inputWrappers).to.have.length(2)
+ })
- });
- });
+ })
+ })
describe('removeWrapper', () => {
- let remove, removeForType, setterFn, getterFn, getByTypeFn;
- const template = 'Something cool
';
- const name = 'name';
- const types = ['input', 'checkbox'];
- const types2 = ['input', 'something else'];
- const types3 = ['checkbox', 'something else'];
+ let remove, removeForType, setterFn, getterFn, getByTypeFn
+ const template = 'Something cool
'
+ const name = 'name'
+ const types = ['input', 'checkbox']
+ const types2 = ['input', 'something else']
+ const types3 = ['checkbox', 'something else']
beforeEach(inject((formlyConfig) => {
- remove = formlyConfig.removeWrapperByName;
- removeForType = formlyConfig.removeWrappersForType;
- setterFn = formlyConfig.setWrapper;
- getterFn = formlyConfig.getWrapper;
- getByTypeFn = formlyConfig.getWrapperByType;
- }));
+ remove = formlyConfig.removeWrapperByName
+ removeForType = formlyConfig.removeWrappersForType
+ setterFn = formlyConfig.setWrapper
+ getterFn = formlyConfig.getWrapper
+ getByTypeFn = formlyConfig.getWrapperByType
+ }))
it('should allow you to remove a wrapper', () => {
- setterFn(template, name);
- remove(name);
- expect(getterFn(name)).to.be.empty;
- });
+ setterFn(template, name)
+ remove(name)
+ expect(getterFn(name)).to.be.empty
+ })
it('should allow you to remove a wrapper for a type', () => {
- setterFn({types, template});
- setterFn({types: types2, template});
- const checkboxAndSomethingElseWrapper = setterFn({types: types3, template});
- removeForType('input');
- expect(getByTypeFn('input')).to.be.empty;
- const checkboxWrappers = getByTypeFn('checkbox');
- expect(checkboxWrappers).to.eql([checkboxAndSomethingElseWrapper]);
- });
- });
-
-
- describe('setType/getType', () => {
- let getterFn, setterFn;
- const name = 'input';
- const template = ' ';
- const templateUrl = '/input.html';
- const wrapper = 'input';
- const wrapper2 = 'input2';
+ setterFn({types, template})
+ setterFn({types: types2, template})
+ const checkboxAndSomethingElseWrapper = setterFn({types: types3, template})
+ removeForType('input')
+ expect(getByTypeFn('input')).to.be.empty
+ const checkboxWrappers = getByTypeFn('checkbox')
+ expect(checkboxWrappers).to.eql([checkboxAndSomethingElseWrapper])
+ })
+ })
+
+
+ describe('setType/getType/getTypes', () => {
+ let getterFn, setterFn, getTypesFn
+ const name = 'input'
+ const template = ' '
+ const templateUrl = '/input.html'
+ const wrapper = 'input'
+ const wrapper2 = 'input2'
beforeEach(inject(function(formlyConfig) {
- getterFn = formlyConfig.getType;
- setterFn = formlyConfig.setType;
- }));
+ getterFn = formlyConfig.getType
+ setterFn = formlyConfig.setType
+ getTypesFn = formlyConfig.getTypes
+ }))
describe('\(^O^)/ path', () => {
it('should accept an object with a name and a template', () => {
- setterFn({name, template});
- expect(getterFn(name).template).to.equal(template);
- });
+ setterFn({name, template})
+ expect(getterFn(name).template).to.equal(template)
+ })
it('should accept an object with a name and a templateUrl', () => {
- setterFn({name, templateUrl});
- expect(getterFn(name).templateUrl).to.equal(templateUrl);
- });
+ setterFn({name, templateUrl})
+ expect(getterFn(name).templateUrl).to.equal(templateUrl)
+ })
it('should accept an array of objects', () => {
setterFn([
{name, template},
- {name: 'type2', templateUrl}
- ]);
- expect(getterFn(name).template).to.equal(template);
- expect(getterFn('type2').templateUrl).to.equal(templateUrl);
- });
+ {name: 'type2', templateUrl},
+ ])
+ expect(getterFn(name).template).to.equal(template)
+ expect(getterFn('type2').templateUrl).to.equal(templateUrl)
+ })
+
+ it('should expose the mapping from type name to config', () => {
+ setterFn([
+ {name, template},
+ {name: 'type2', templateUrl},
+ ])
+ expect(getTypesFn()).to.eql({[name]: getterFn(name), type2: getterFn('type2')})
+ })
it('should allow you to set a wrapper as a string', () => {
- setterFn({name, template, wrapper});
- expect(getterFn(name).wrapper).to.equal(wrapper);
- });
+ setterFn({name, template, wrapper})
+ expect(getterFn(name).wrapper).to.equal(wrapper)
+ })
it('should allow you to set a wrapper as an array', () => {
- setterFn({name, template, wrapper: [wrapper, wrapper2]});
- expect(getterFn(name).wrapper).to.eql([wrapper, wrapper2]);
- });
+ setterFn({name, template, wrapper: [wrapper, wrapper2]})
+ expect(getterFn(name).wrapper).to.eql([wrapper, wrapper2])
+ })
describe(`extends`, () => {
describe(`object case`, () => {
@@ -237,9 +246,9 @@ describe('formlyConfig', () => {
defaultOptions: {
templateOptions: {
required: true,
- min: 3
- }
- }
+ min: 3,
+ },
+ },
},
{
name: 'type2',
@@ -247,337 +256,399 @@ describe('formlyConfig', () => {
defaultOptions: {
templateOptions: {
required: false,
- max: 4
+ max: 4,
},
data: {
- extraStuff: [1, 2, 3]
- }
- }
- }
- ]);
- });
+ extraStuff: [1, 2, 3],
+ },
+ },
+ },
+ ])
+ })
it(`should inherit all fields that it does not have itself`, () => {
- expect(getterFn('type2').template).to.eql(template);
- });
+ expect(getterFn('type2').template).to.eql(template)
+ })
it(`should merge objects that it shares`, () => {
expect(getterFn('type2').defaultOptions).to.eql({
templateOptions: {
required: false,
min: 3,
- max: 4
+ max: 4,
},
data: {
- extraStuff: [1, 2, 3]
- }
- });
- });
+ extraStuff: [1, 2, 3],
+ },
+ })
+ })
it(`should not error when extends is specified without a template, templateUrl, or defaultOptions`, () => {
- expect(() => setterFn({name: 'type3', extends: 'type2'})).to.not.throw();
- });
+ expect(() => setterFn({name: 'type3', extends: 'type2'})).to.not.throw()
+ })
+
+ })
+
+ describe(`abstractType function case`, () => {
+ beforeEach(() => {
+ setterFn([
+ {
+ name,
+ template,
+ defaultOptions: function(options) {
+ return {
+ templateOptions: {
+ required: true,
+ min: 3,
+ },
+ }
+ },
+ },
+ {
+ name: 'type2',
+ extends: name,
+ defaultOptions: function(options) {
+ return {
+ templateOptions: {
+ required: false,
+ max: 4,
+ },
+ }
+ },
+ },
+ {
+ name: 'type3',
+ extends: name,
+ defaultOptions: {
+ templateOptions: {
+ required: false,
+ max: 4,
+ },
+ },
+ },
+ ])
+ })
- });
+ it(`should merge options when extending defaultOptions is a function`, () => {
+ expect(getterFn('type2').defaultOptions({})).to.eql({
+ templateOptions: {
+ required: false,
+ min: 3,
+ max: 4,
+ },
+ })
+ })
+
+ it(`should merge options when extending defaultOptions is an object`, () => {
+ expect(getterFn('type3').defaultOptions({})).to.eql({
+ templateOptions: {
+ required: false,
+ min: 3,
+ max: 4,
+ },
+ })
+ })
+
+ })
describe(`template/templateUrl Cases`, () => {
it('should use templateUrl if type defines it and its parent has template defined', function() {
setterFn([
{
name,
- template
+ template,
},
{
name: 'type2',
extends: name,
- templateUrl
- }
- ]);
+ templateUrl,
+ },
+ ])
- expect(getterFn('type2').templateUrl).not.to.be.undefined;
- expect(getterFn('type2').template).to.be.undefined;
- });
+ expect(getterFn('type2').templateUrl).not.to.be.undefined
+ expect(getterFn('type2').template).to.be.undefined
+ })
it('should use template if type defines it and its parent had templateUrl defined', function() {
setterFn([
{
name,
- templateUrl
+ templateUrl,
},
{
name: 'type2',
extends: name,
- template
- }
- ]);
+ template,
+ },
+ ])
- expect(getterFn('type2').template).not.to.be.undefined;
- expect(getterFn('type2').templateUrl).to.be.undefined;
- });
- });
+ expect(getterFn('type2').template).not.to.be.undefined
+ expect(getterFn('type2').templateUrl).to.be.undefined
+ })
+ })
describe(`function cases`, () => {
- let args, fakeScope, parentFn, childFn, parentDefaultOptions, childDefaultOptions, argsAndParent;
+ let args, fakeScope, parentFn, childFn, parentDefaultOptions, childDefaultOptions, argsAndParent
beforeEach(() => {
- args = {data: {someData: true}};
- fakeScope = {};
+ args = {data: {someData: true}}
+ fakeScope = {}
parentDefaultOptions = {
data: {extraOptions: true},
- templateOptions: {placeholder: 'hi'}
- };
+ templateOptions: {placeholder: 'hi'},
+ }
childDefaultOptions = {
- templateOptions: {placeholder: 'hey', required: true}
- };
- parentFn = sinon.stub().returns(parentDefaultOptions);
- childFn = sinon.stub().returns(childDefaultOptions);
+ templateOptions: {placeholder: 'hey', required: true},
+ }
+ parentFn = sinon.stub().returns(parentDefaultOptions)
+ childFn = sinon.stub().returns(childDefaultOptions)
argsAndParent = {
data: {someData: true, extraOptions: true},
- templateOptions: {placeholder: 'hi'}
- };
- });
+ templateOptions: {placeholder: 'hi'},
+ }
+ })
it(`should call the extended parent's defaultOptions function and its own defaultOptions function`, () => {
setterFn([
{name, defaultOptions: parentFn},
- {name: 'type2', extends: name, defaultOptions: childFn}
- ]);
- getterFn('type2').defaultOptions(args, fakeScope);
- expect(parentFn).to.have.been.calledWith(args, fakeScope);
- expect(childFn).to.have.been.calledWith(argsAndParent, fakeScope);
- });
+ {name: 'type2', extends: name, defaultOptions: childFn},
+ ])
+ getterFn('type2').defaultOptions(args, fakeScope)
+ expect(parentFn).to.have.been.calledWith(args, fakeScope)
+ expect(childFn).to.have.been.calledWith(argsAndParent, fakeScope)
+ })
it(`should call the extended parent's defaultOptions function when it doesn't have one of its own`, () => {
setterFn([
{name, defaultOptions: parentFn},
- {name: 'type2', extends: name}
- ]);
- getterFn('type2').defaultOptions(args, fakeScope);
- expect(parentFn).to.have.been.calledWith(args, fakeScope);
- });
+ {name: 'type2', extends: name},
+ ])
+ getterFn('type2').defaultOptions(args, fakeScope)
+ expect(parentFn).to.have.been.calledWith(args, fakeScope)
+ })
it(`should call its own defaultOptions function when the parent doesn't have one`, () => {
setterFn([
{name, template},
- {name: 'type2', extends: name, defaultOptions: childFn}
- ]);
- getterFn('type2').defaultOptions(args, fakeScope);
- expect(childFn).to.have.been.calledWith(args, fakeScope);
- });
+ {name: 'type2', extends: name, defaultOptions: childFn},
+ ])
+ getterFn('type2').defaultOptions(args, fakeScope)
+ expect(childFn).to.have.been.calledWith(args, fakeScope)
+ })
it(`should extend its defaultOptions object with the parent's defaultOptions object`, () => {
const objectMergedDefaultOptions = {
data: {extraOptions: true},
- templateOptions: {placeholder: 'hey', required: true}
- };
+ templateOptions: {placeholder: 'hey', required: true},
+ }
setterFn([
{name, defaultOptions: parentDefaultOptions},
- {name: 'type2', extends: name, defaultOptions: childDefaultOptions}
- ]);
- expect(getterFn('type2').defaultOptions).to.eql(objectMergedDefaultOptions);
- });
+ {name: 'type2', extends: name, defaultOptions: childDefaultOptions},
+ ])
+ expect(getterFn('type2').defaultOptions).to.eql(objectMergedDefaultOptions)
+ })
it(`should call its defaultOptions with the parent's defaultOptions object merged with the given args`, () => {
setterFn([
{name, defaultOptions: parentDefaultOptions},
- {name: 'type2', extends: name, defaultOptions: childFn}
- ]);
- const returned = getterFn('type2').defaultOptions(args, fakeScope);
- expect(childFn).to.have.been.calledWith(argsAndParent, fakeScope);
- expect(returned).to.eql(childDefaultOptions);
- });
- });
+ {name: 'type2', extends: name, defaultOptions: childFn},
+ ])
+ const returned = getterFn('type2').defaultOptions(args, fakeScope)
+ expect(childFn).to.have.been.calledWith(argsAndParent, fakeScope)
+ expect(returned).to.eql(childDefaultOptions)
+ })
+ })
describe(`link functions`, () => {
- let linkArgs, parentFn, childFn;
+ let linkArgs, parentFn, childFn
beforeEach(inject(($rootScope) => {
- linkArgs = [$rootScope.$new(), angular.element('
'), {}];
- parentFn = sinon.spy();
- childFn = sinon.spy();
- }));
+ linkArgs = [$rootScope.$new(), angular.element('
'), {}]
+ parentFn = sinon.spy()
+ childFn = sinon.spy()
+ }))
it(`should call the parent link function when there is no child function`, () => {
setterFn([
{name, template, link: parentFn},
- {name: 'type2', extends: name}
- ]);
- getterFn('type2').link(...linkArgs);
- expect(parentFn).to.have.been.calledWith(...linkArgs);
- });
+ {name: 'type2', extends: name},
+ ])
+ getterFn('type2').link(...linkArgs)
+ expect(parentFn).to.have.been.calledWith(...linkArgs)
+ })
it(`should call the child link function when there is no parent function`, () => {
setterFn([
{name, template},
- {name: 'type2', extends: name, link: childFn}
- ]);
- getterFn('type2').link(...linkArgs);
- expect(childFn).to.have.been.calledWith(...linkArgs);
- });
+ {name: 'type2', extends: name, link: childFn},
+ ])
+ getterFn('type2').link(...linkArgs)
+ expect(childFn).to.have.been.calledWith(...linkArgs)
+ })
it(`should call the child link function and the parent link function when they are both present`, () => {
setterFn([
{name, template, link: parentFn},
- {name: 'type2', extends: name, link: childFn}
- ]);
- getterFn('type2').link(...linkArgs);
- expect(parentFn).to.have.been.calledWith(...linkArgs);
- expect(childFn).to.have.been.calledWith(...linkArgs);
- });
+ {name: 'type2', extends: name, link: childFn},
+ ])
+ getterFn('type2').link(...linkArgs)
+ expect(parentFn).to.have.been.calledWith(...linkArgs)
+ expect(childFn).to.have.been.calledWith(...linkArgs)
+ })
- });
+ })
describe(`controller functions`, () => {
- let parentFn, childFn, $controller, $scope;
+ let parentFn, childFn, $controller, $scope
beforeEach(inject(($rootScope, _$controller_) => {
- $scope = $rootScope.$new();
- $controller = _$controller_;
- parentFn = sinon.spy();
- parentFn.$inject = ['$log'];
- childFn = sinon.spy();
- childFn.$inject = ['$http'];
- }));
+ $scope = $rootScope.$new()
+ $controller = _$controller_
+ parentFn = sinon.spy()
+ parentFn.$inject = ['$log']
+ childFn = sinon.spy()
+ childFn.$inject = ['$http']
+ }))
it(`should call the parent controller function when there is no child controller function`, inject(($log) => {
setterFn([
{name, template, controller: parentFn},
- {name: 'type2', extends: name}
- ]);
- $controller(getterFn('type2').controller, {$scope});
- expect(parentFn).to.have.been.calledWith($log);
- }));
+ {name: 'type2', extends: name},
+ ])
+ $controller(getterFn('type2').controller, {$scope})
+ expect(parentFn).to.have.been.calledWith($log)
+ }))
it(`should call the parent controller function and the child's when there is a child controller function`, inject(($log, $http) => {
setterFn([
{name, template, controller: parentFn},
- {name: 'type2', extends: name, controller: childFn}
- ]);
- $controller(getterFn('type2').controller, {$scope});
- expect(parentFn).to.have.been.calledWith($log);
- expect(childFn).to.have.been.calledWith($http);
- }));
+ {name: 'type2', extends: name, controller: childFn},
+ ])
+ $controller(getterFn('type2').controller, {$scope})
+ expect(parentFn).to.have.been.calledWith($log)
+ expect(childFn).to.have.been.calledWith($http)
+ }))
it(`should call the child controller function when there's no parent controller`, inject(($http) => {
setterFn([
{name, template},
- {name: 'type2', extends: name, controller: childFn}
- ]);
- $controller(getterFn('type2').controller, {$scope});
- expect(childFn).to.have.been.calledWith($http);
- }));
+ {name: 'type2', extends: name, controller: childFn},
+ ])
+ $controller(getterFn('type2').controller, {$scope})
+ expect(childFn).to.have.been.calledWith($http)
+ }))
- });
- });
- });
+ })
+ })
+ })
describe('(◞‸◟;) path', () => {
it('should throw an error when the first argument is not an object or an array', () => {
- expect(() => setterFn('string')).to.throw(/must.*provide.*object.*array/);
- expect(() => setterFn(324)).to.throw(/must.*provide.*object.*array/);
- expect(() => setterFn(false)).to.throw(/must.*provide.*object.*array/);
- });
+ expect(() => setterFn('string')).to.throw(/must.*provide.*object.*array/)
+ expect(() => setterFn(324)).to.throw(/must.*provide.*object.*array/)
+ expect(() => setterFn(false)).to.throw(/must.*provide.*object.*array/)
+ })
it('should throw an error when a name is not provided', () => {
- expect(() => setterFn({templateUrl})).to.throw(/formlyConfig\.setType/);
- });
+ expect(() => setterFn({templateUrl})).to.throw(/formlyConfig\.setType/)
+ })
it(`should throw an error when specifying both a template and a templateUrl`, () => {
- expect(() => setterFn({name, template, templateUrl})).to.throw(/formlyConfig\.setType/);
- });
+ expect(() => setterFn({name, template, templateUrl})).to.throw(/formlyConfig\.setType/)
+ })
it(`should throw an error when an extra property is provided`, () => {
- expect(() => setterFn({name, templateUrl, extra: true})).to.throw(/formlyConfig\.setType/);
- });
+ expect(() => setterFn({name, templateUrl, extra: true})).to.throw(/formlyConfig\.setType/)
+ })
it('should warn when attempting to override a type', () => {
shouldWarn(/overwrite/, function() {
- setterFn({name, template});
- setterFn({name, template});
- });
- });
- });
+ setterFn({name, template})
+ setterFn({name, template})
+ })
+ })
+ })
describe(`apiCheck`, () => {
- testApiCheck('setType', 'getType');
- });
- });
+ testApiCheck('setType', 'getType')
+ })
+ })
function testApiCheck(setterName, getterName) {
- const template = 'something with ';
- const name = 'input';
- let setterFn, getterFn, formlyApiCheck;
+ const template = 'something with '
+ const name = 'input'
+ let setterFn, getterFn, formlyApiCheck
beforeEach(inject((_formlyApiCheck_, formlyConfig) => {
- formlyApiCheck = _formlyApiCheck_;
- setterFn = formlyConfig[setterName];
- getterFn = formlyConfig[getterName];
- }));
+ formlyApiCheck = _formlyApiCheck_
+ setterFn = formlyConfig[setterName]
+ getterFn = formlyConfig[getterName]
+ }))
it(`should allow you to specify an apiCheck function that will be used to validate your options`, () => {
expect(() => {
setterFn({
name,
apiCheck,
- template
- });
- }).to.not.throw();
+ template,
+ })
+ }).to.not.throw()
- expect(getterFn(name).apiCheck).to.equal(apiCheck);
+ expect(getterFn(name).apiCheck).to.equal(apiCheck)
function apiCheck() {
return {
templateOptions: {},
- data: {}
- };
+ data: {},
+ }
}
- });
+ })
describe(`apiCheckInstance`, () => {
- let apiCheckInstance;
+ let apiCheckInstance
beforeEach(() => {
- apiCheckInstance = require('api-check')();
- });
+ apiCheckInstance = require('api-check')()
+ })
it(`should allow you to specify an instance of your own apiCheck so messaging will be custom`, () => {
expect(() => {
- setterFn({name, apiCheck, apiCheckInstance, template});
- }).to.not.throw();
- expect(getterFn(name).apiCheckInstance).to.equal(apiCheckInstance);
- });
+ setterFn({name, apiCheck, apiCheckInstance, template})
+ }).to.not.throw()
+ expect(getterFn(name).apiCheckInstance).to.equal(apiCheckInstance)
+ })
it(`should throw an error if you specify an instance without specifying an apiCheck`, () => {
expect(() => {
- setterFn({name, apiCheckInstance, template});
- }).to.throw();
- });
+ setterFn({name, apiCheckInstance, template})
+ }).to.throw()
+ })
function apiCheck() {
return {
templateOptions: {},
- data: {}
- };
+ data: {},
+ }
}
- });
+ })
describe(`apiCheckFunction`, () => {
it(`should allow you to specify warn or throw as the `, () => {
expect(() => {
- setterFn({name, apiCheck, apiCheckFunction: 'warn', template});
- }).to.not.throw();
- expect(getterFn(name).apiCheckFunction).to.equal('warn');
+ setterFn({name, apiCheck, apiCheckFunction: 'warn', template})
+ }).to.not.throw()
+ expect(getterFn(name).apiCheckFunction).to.equal('warn')
expect(() => {
- setterFn({name: 'name2', apiCheck, apiCheckFunction: 'throw', template});
- }).to.not.throw();
- expect(getterFn('name2').apiCheckFunction).to.equal('throw');
- });
+ setterFn({name: 'name2', apiCheck, apiCheckFunction: 'throw', template})
+ }).to.not.throw()
+ expect(getterFn('name2').apiCheckFunction).to.equal('throw')
+ })
it(`should throw an error if you specify anything other than warn or throw`, () => {
expect(() => {
- setterFn({name, apiCheckFunction: 'other', template});
- }).to.throw();
- });
+ setterFn({name, apiCheckFunction: 'other', template})
+ }).to.throw()
+ })
function apiCheck() {
return {
templateOptions: {},
- data: {}
- };
+ data: {},
+ }
}
- });
+ })
}
@@ -585,89 +656,89 @@ describe('formlyConfig', () => {
describe(`that impact field rendering`, () => {
- let scope, $compile, el, field;
+ let scope, $compile, el, field
beforeEach(inject(($rootScope, _$compile_) => {
- scope = $rootScope.$new();
- $compile = _$compile_;
- scope.fields = [{template: ' '}];
- }));
+ scope = $rootScope.$new()
+ $compile = _$compile_
+ scope.fields = [{template: ' '}]
+ }))
describe(`defaultHideDirective`, () => {
it(`should default formly-form to use ng-if when not specified`, () => {
compileAndDigest(`
- `);
- const fieldNode = getFieldNode();
- expect(fieldNode.getAttribute('ng-if')).to.exist;
- });
+ `)
+ const fieldNode = getFieldNode()
+ expect(fieldNode.getAttribute('ng-if')).to.exist
+ })
it(`should default formly-form to use the specified directive for hiding and showing`, () => {
- formlyConfig.extras.defaultHideDirective = 'ng-show';
+ formlyConfig.extras.defaultHideDirective = 'ng-show'
compileAndDigest(`
- `);
- const fieldNode = getFieldNode();
- expect(fieldNode.getAttribute('ng-show')).to.exist;
- });
+ `)
+ const fieldNode = getFieldNode()
+ expect(fieldNode.getAttribute('ng-show')).to.exist
+ })
it(`should be overrideable on a per-form basis`, () => {
- formlyConfig.extras.defaultHideDirective = '(╯°□°)╯︵ ┻━┻';
+ formlyConfig.extras.defaultHideDirective = '(╯°□°)╯︵ ┻━┻'
compileAndDigest(`
- `);
- const fieldNode = getFieldNode();
- expect(fieldNode.getAttribute('ng-show')).to.exist;
- expect(fieldNode.getAttribute('(╯°□°)╯︵ ┻━┻')).to.not.exist;
- });
+ `)
+ const fieldNode = getFieldNode()
+ expect(fieldNode.getAttribute('ng-show')).to.exist
+ expect(fieldNode.getAttribute('(╯°□°)╯︵ ┻━┻')).to.not.exist
+ })
- });
+ })
describe(`getFieldId`, () => {
it(`should allow you to specify your own function for generating the IDs for a field`, () => {
scope.fields = [
getNewField({id: 'custom'}),
getNewField({model: {foo: 'bar', id: '1234'}, key: 'foo'}),
- getNewField({key: 'bar'})
- ];
+ getNewField({key: 'bar'}),
+ ]
formlyConfig.extras.getFieldId = function(options, model, scope) {
if (options.id) {
- return options.id;
+ return options.id
}
- return [scope.index, (model && model.id) || 'new-model', options.key].join('_');
- };
- compileAndDigest();
+ return [scope.index, (model && model.id) || 'new-model', options.key].join('_')
+ }
+ compileAndDigest()
- const field0 = getFieldNgModelNode(0);
- const field1 = getFieldNgModelNode(1);
- const field2 = getFieldNgModelNode(2);
+ const field0 = getFieldNgModelNode(0)
+ const field1 = getFieldNgModelNode(1)
+ const field2 = getFieldNgModelNode(2)
- expect(field0.id).to.eq('custom');
- expect(field1.id).to.eq('1_1234_foo');
- expect(field2.id).to.eq('2_new-model_bar');
- });
- });
+ expect(field0.id).to.eq('custom')
+ expect(field1.id).to.eq('1_1234_foo')
+ expect(field2.id).to.eq('2_new-model_bar')
+ })
+ })
function compileAndDigest(template) {
- el = $compile(template || basicForm)(scope);
- scope.$digest();
- field = scope.fields[0];
- return el;
+ el = $compile(template || basicForm)(scope)
+ scope.$digest()
+ field = scope.fields[0]
+ return el
}
function getFieldNode(index = 0) {
- return el[0].querySelectorAll('.formly-field')[index];
+ return el[0].querySelectorAll('.formly-field')[index]
}
function getFieldNgModelNode(index = 0) {
- return getFieldNode(index).querySelector('[ng-model]');
+ return getFieldNode(index).querySelector('[ng-model]')
}
- });
+ })
- });
+ })
describe(`getTypeHeritage`, () => {
it(`should get the heritage of all type extensions`, () => {
@@ -676,11 +747,11 @@ describe('formlyConfig', () => {
{name: 'parent', extends: 'grandparent'},
{name: 'child', extends: 'parent'},
{name: 'extra', extends: 'grandparent'},
- {name: 'extra2'}
- ]);
+ {name: 'extra2'},
+ ])
expect(formlyConfig.getTypeHeritage('child')).to.eql([
- formlyConfig.getType('parent'), formlyConfig.getType('grandparent')
- ]);
- });
- });
-});
+ formlyConfig.getType('parent'), formlyConfig.getType('grandparent'),
+ ])
+ })
+ })
+})
diff --git a/src/providers/formlyUsability.js b/src/providers/formlyUsability.js
index f9ac7363..87e9af4f 100644
--- a/src/providers/formlyUsability.js
+++ b/src/providers/formlyUsability.js
@@ -1,6 +1,6 @@
-import angular from 'angular-fix';
+import angular from 'angular-fix'
-export default formlyUsability;
+export default formlyUsability
// @ngInject
function formlyUsability(formlyApiCheck, formlyErrorAndWarningsUrlPrefix) {
@@ -10,49 +10,49 @@ function formlyUsability(formlyApiCheck, formlyErrorAndWarningsUrlPrefix) {
checkWrapper,
checkWrapperTemplate,
getErrorMessage,
- $get: () => this
- });
+ $get: () => this,
+ })
function getFieldError(errorInfoSlug, message, field) {
if (arguments.length < 3) {
- field = message;
- message = errorInfoSlug;
- errorInfoSlug = null;
+ field = message
+ message = errorInfoSlug
+ errorInfoSlug = null
}
- return new Error(getErrorMessage(errorInfoSlug, message) + ` Field definition: ${angular.toJson(field)}`);
+ return new Error(getErrorMessage(errorInfoSlug, message) + ` Field definition: ${angular.toJson(field)}`)
}
function getFormlyError(errorInfoSlug, message) {
if (!message) {
- message = errorInfoSlug;
- errorInfoSlug = null;
+ message = errorInfoSlug
+ errorInfoSlug = null
}
- return new Error(getErrorMessage(errorInfoSlug, message));
+ return new Error(getErrorMessage(errorInfoSlug, message))
}
function getErrorMessage(errorInfoSlug, message) {
- let url = '';
+ let url = ''
if (errorInfoSlug !== null) {
- url = `${formlyErrorAndWarningsUrlPrefix}${errorInfoSlug}`;
+ url = `${formlyErrorAndWarningsUrlPrefix}${errorInfoSlug}`
}
- return `Formly Error: ${message}. ${url}`;
+ return `Formly Error: ${message}. ${url}`
}
function checkWrapper(wrapper) {
formlyApiCheck.throw(formlyApiCheck.formlyWrapperType, wrapper, {
prefix: 'formlyConfig.setWrapper',
- urlSuffix: 'setwrapper-validation-failed'
- });
+ urlSuffix: 'setwrapper-validation-failed',
+ })
}
function checkWrapperTemplate(template, additionalInfo) {
- const formlyTransclude = ' ';
+ const formlyTransclude = ' '
if (template.indexOf(formlyTransclude) === -1) {
throw getFormlyError(
`Template wrapper templates must use "${formlyTransclude}" somewhere in them. ` +
`This one does not have " " in it: ${template}` + '\n' +
`Additional information: ${JSON.stringify(additionalInfo)}`
- );
+ )
}
}
}
diff --git a/src/providers/formlyValidationMessages.js b/src/providers/formlyValidationMessages.js
index ef3067af..4b5edc01 100644
--- a/src/providers/formlyValidationMessages.js
+++ b/src/providers/formlyValidationMessages.js
@@ -1,4 +1,4 @@
-export default formlyValidationMessages;
+export default formlyValidationMessages
// @ngInject
@@ -7,27 +7,27 @@ function formlyValidationMessages() {
const validationMessages = {
addTemplateOptionValueMessage,
addStringMessage,
- messages: {}
- };
+ messages: {},
+ }
- return validationMessages;
+ return validationMessages
function addTemplateOptionValueMessage(name, prop, prefix, suffix, alternate) {
- validationMessages.messages[name] = templateOptionValue(prop, prefix, suffix, alternate);
+ validationMessages.messages[name] = templateOptionValue(prop, prefix, suffix, alternate)
}
function addStringMessage(name, string) {
- validationMessages.messages[name] = () => string;
+ validationMessages.messages[name] = () => string
}
function templateOptionValue(prop, prefix, suffix, alternate) {
return function getValidationMessage(viewValue, modelValue, scope) {
- if (scope.options.templateOptions[prop]) {
- return `${prefix} ${scope.options.templateOptions[prop]} ${suffix}`;
+ if (typeof scope.options.templateOptions[prop] !== 'undefined') {
+ return `${prefix} ${scope.options.templateOptions[prop]} ${suffix}`
} else {
- return alternate;
+ return alternate
}
- };
+ }
}
}
diff --git a/src/run/formlyCustomTags.js b/src/run/formlyCustomTags.js
index ff2a4988..e0714a8f 100644
--- a/src/run/formlyCustomTags.js
+++ b/src/run/formlyCustomTags.js
@@ -1,24 +1,18 @@
-import angular from 'angular-fix';
-export default addCustomTags;
+import angular from 'angular-fix'
+export default addCustomTags
// @ngInject
function addCustomTags($document) {
- if ($document && $document.get) {
- // IE8 check ->
- // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript/10965203#10965203
- const document = $document.get(0);
- const div = document.createElement('div');
- div.innerHTML = '';
- const isIeLessThan9 = (div.getElementsByTagName('i').length === 1);
-
- if (isIeLessThan9) {
- // add the custom elements that we need for formly
- const customElements = [
- 'formly-field', 'formly-form', 'formly-custom-validation', 'formly-focus', 'formly-transpose'
- ];
- angular.forEach(customElements, el => {
- document.createElement(el);
- });
- }
+ // IE8 check ->
+ // https://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx
+ if ($document && $document.documentMode < 9) {
+ const document = $document.get(0)
+ // add the custom elements that we need for formly
+ const customElements = [
+ 'formly-field', 'formly-form',
+ ]
+ angular.forEach(customElements, el => {
+ document.createElement(el)
+ })
}
}
diff --git a/src/run/formlyCustomTags.test.js b/src/run/formlyCustomTags.test.js
index e9b38fcc..c176e7ff 100644
--- a/src/run/formlyCustomTags.test.js
+++ b/src/run/formlyCustomTags.test.js
@@ -1,35 +1,32 @@
-import angular from 'angular';
+import angular from 'angular'
describe(`formlyCustomTags`, () => {
beforeEach(window.module(`formly`, $provide => {
- const docStub = {
+ $provide.value(`$document`, {
+ documentMode: 8,
get: sinon.stub().withArgs(0).returnsThis(),
- createElement: sinon.stub().withArgs(`div`).returns({
- getElementsByTagName: sinon.stub().withArgs(`i`).returns([1])
- })
- };
-
- $provide.value(`$document`, docStub);
- }));
+ createElement: sinon.spy(),
+ })
+ }))
- let $document;
+ let $document
beforeEach(inject((_$document_) => {
- $document = _$document_;
- }));
+ $document = _$document_
+ }))
describe(`addCustomTags`, () => {
it(`should create custom formly tags`, () => {
const customElements = [
- `div`, `formly-field`, `formly-form`, `formly-custom-validation`, `formly-focus`, `formly-transpose`
- ];
+ `formly-field`, `formly-form`,
+ ]
- expect($document.get).to.have.been.calledOnce;
+ expect($document.get).to.have.been.calledOnce
angular.forEach(customElements, el => {
- expect($document.createElement).to.have.been.calledWith(el);
- });
- });
- });
-});
+ expect($document.createElement).to.have.been.calledWith(el)
+ })
+ })
+ })
+})
diff --git a/src/run/formlyNgModelAttrsManipulator.js b/src/run/formlyNgModelAttrsManipulator.js
index 1db56893..e780b9ee 100644
--- a/src/run/formlyNgModelAttrsManipulator.js
+++ b/src/run/formlyNgModelAttrsManipulator.js
@@ -1,59 +1,59 @@
-import angular from 'angular-fix';
-import {contains} from '../other/utils';
+import angular from 'angular-fix'
+import {contains} from '../other/utils'
-export default addFormlyNgModelAttrsManipulator;
+export default addFormlyNgModelAttrsManipulator
// @ngInject
function addFormlyNgModelAttrsManipulator(formlyConfig, $interpolate) {
if (formlyConfig.extras.disableNgModelAttrsManipulator) {
- return;
+ return
}
- formlyConfig.templateManipulators.preWrapper.push(ngModelAttrsManipulator);
+ formlyConfig.templateManipulators.preWrapper.push(ngModelAttrsManipulator)
function ngModelAttrsManipulator(template, options, scope) {
- const node = document.createElement('div');
- const skip = options.extras && options.extras.skipNgModelAttrsManipulator;
+ const node = document.createElement('div')
+ const skip = options.extras && options.extras.skipNgModelAttrsManipulator
if (skip === true) {
- return template;
+ return template
}
- node.innerHTML = template;
+ node.innerHTML = template
- const modelNodes = getNgModelNodes(node, skip);
+ const modelNodes = getNgModelNodes(node, skip)
if (!modelNodes || !modelNodes.length) {
- return template;
+ return template
}
- addIfNotPresent(modelNodes, 'id', scope.id);
- addIfNotPresent(modelNodes, 'name', scope.name || scope.id);
+ addIfNotPresent(modelNodes, 'id', scope.id)
+ addIfNotPresent(modelNodes, 'name', scope.name || scope.id)
- addValidation();
- alterNgModelAttr();
- addModelOptions();
- addTemplateOptionsAttrs();
- addNgModelElAttrs();
+ addValidation()
+ alterNgModelAttr()
+ addModelOptions()
+ addTemplateOptionsAttrs()
+ addNgModelElAttrs()
- return node.innerHTML;
+ return node.innerHTML
function addValidation() {
if (angular.isDefined(options.validators) || angular.isDefined(options.validation.messages)) {
- addIfNotPresent(modelNodes, 'formly-custom-validation', '');
+ addIfNotPresent(modelNodes, 'formly-custom-validation', '')
}
}
function alterNgModelAttr() {
if (isPropertyAccessor(options.key)) {
- addRegardlessOfPresence(modelNodes, 'ng-model', 'model.' + options.key);
+ addRegardlessOfPresence(modelNodes, 'ng-model', 'model.' + options.key)
}
}
function addModelOptions() {
if (angular.isDefined(options.modelOptions)) {
- addIfNotPresent(modelNodes, 'ng-model-options', 'options.modelOptions');
+ addIfNotPresent(modelNodes, 'ng-model-options', 'options.modelOptions')
if (options.modelOptions.getterSetter) {
- addRegardlessOfPresence(modelNodes, 'ng-model', 'options.value');
+ addRegardlessOfPresence(modelNodes, 'ng-model', 'options.value')
}
}
}
@@ -61,178 +61,178 @@ function addFormlyNgModelAttrsManipulator(formlyConfig, $interpolate) {
function addTemplateOptionsAttrs() {
if (!options.templateOptions && !options.expressionProperties) {
// no need to run these if there are no templateOptions or expressionProperties
- return;
+ return
}
- const to = options.templateOptions || {};
- const ep = options.expressionProperties || {};
+ const to = options.templateOptions || {}
+ const ep = options.expressionProperties || {}
- const ngModelAttributes = getBuiltInAttributes();
+ const ngModelAttributes = getBuiltInAttributes()
// extend with the user's specifications winning
- angular.extend(ngModelAttributes, options.ngModelAttrs);
+ angular.extend(ngModelAttributes, options.ngModelAttrs)
// Feel free to make this more simple :-)
angular.forEach(ngModelAttributes, (val, name) => {
/* eslint complexity:[2, 14] */
- let attrVal, attrName;
- const ref = `options.templateOptions['${name}']`;
- const toVal = to[name];
- const epVal = getEpValue(ep, name);
+ let attrVal, attrName
+ const ref = `options.templateOptions['${name}']`
+ const toVal = to[name]
+ const epVal = getEpValue(ep, name)
- const inTo = angular.isDefined(toVal);
- const inEp = angular.isDefined(epVal);
+ const inTo = angular.isDefined(toVal)
+ const inEp = angular.isDefined(epVal)
if (val.value) {
// I realize this looks backwards, but it's right, trust me...
- attrName = val.value;
- attrVal = name;
+ attrName = val.value
+ attrVal = name
} else if (val.statement && inTo) {
- attrName = val.statement;
+ attrName = val.statement
if (angular.isString(to[name])) {
- attrVal = `$eval(${ref})`;
+ attrVal = `$eval(${ref})`
} else if (angular.isFunction(to[name])) {
- attrVal = `${ref}(model[options.key], options, this, $event)`;
+ attrVal = `${ref}(model[options.key], options, this, $event)`
} else {
throw new Error(
`options.templateOptions.${name} must be a string or function: ${JSON.stringify(options)}`
- );
+ )
}
} else if (val.bound && inEp) {
- attrName = val.bound;
- attrVal = ref;
+ attrName = val.bound
+ attrVal = ref
} else if ((val.attribute || val.boolean) && inEp) {
- attrName = val.attribute || val.boolean;
- attrVal = `${$interpolate.startSymbol()}${ref}${$interpolate.endSymbol()}`;
+ attrName = val.attribute || val.boolean
+ attrVal = `${$interpolate.startSymbol()}${ref}${$interpolate.endSymbol()}`
} else if (val.attribute && inTo) {
- attrName = val.attribute;
- attrVal = toVal;
+ attrName = val.attribute
+ attrVal = toVal
} else if (val.boolean) {
if (inTo && !inEp && toVal) {
- attrName = val.boolean;
- attrVal = true;
+ attrName = val.boolean
+ attrVal = true
} else {
/* eslint no-empty:0 */
// empty to illustrate that a boolean will not be added via val.bound
// if you want it added via val.bound, then put it in expressionProperties
}
} else if (val.bound && inTo) {
- attrName = val.bound;
- attrVal = ref;
+ attrName = val.bound
+ attrVal = ref
}
if (angular.isDefined(attrName) && angular.isDefined(attrVal)) {
- addIfNotPresent(modelNodes, attrName, attrVal);
+ addIfNotPresent(modelNodes, attrName, attrVal)
}
- });
+ })
}
function addNgModelElAttrs() {
angular.forEach(options.ngModelElAttrs, (val, name) => {
- addRegardlessOfPresence(modelNodes, name, val);
- });
+ addRegardlessOfPresence(modelNodes, name, val)
+ })
}
}
// Utility functions
function getNgModelNodes(node, skip) {
- const selectorNot = angular.isString(skip) ? `:not(${skip})` : '';
- const skipNot = ':not([formly-skip-ng-model-attrs-manipulator])';
- const query = `[ng-model]${selectorNot}${skipNot}, [data-ng-model]${selectorNot}${skipNot}`;
+ const selectorNot = angular.isString(skip) ? `:not(${skip})` : ''
+ const skipNot = ':not([formly-skip-ng-model-attrs-manipulator])'
+ const query = `[ng-model]${selectorNot}${skipNot}, [data-ng-model]${selectorNot}${skipNot}`
try {
- return node.querySelectorAll(query);
+ return node.querySelectorAll(query)
} catch (e) {
//this code is needed for IE8, as it does not support the CSS3 ':not' selector
//it should be removed when IE8 support is dropped
- return getNgModelNodesFallback(node, skip);
+ return getNgModelNodesFallback(node, skip)
}
}
function getNgModelNodesFallback(node, skip) {
- const allNgModelNodes = node.querySelectorAll('[ng-model], [data-ng-model]');
- const matchingNgModelNodes = [];
+ const allNgModelNodes = node.querySelectorAll('[ng-model], [data-ng-model]')
+ const matchingNgModelNodes = []
//make sure this array is compatible with NodeList type by adding an 'item' function
matchingNgModelNodes.item = function(i) {
- return this[i];
- };
+ return this[i]
+ }
for (let i = 0; i < allNgModelNodes.length; i++) {
- const ngModelNode = allNgModelNodes[i];
+ const ngModelNode = allNgModelNodes[i]
if (!ngModelNode.hasAttribute('formly-skip-ng-model-attrs-manipulator') &&
!(angular.isString(skip) && nodeMatches(ngModelNode, skip))) {
- matchingNgModelNodes.push(ngModelNode);
+ matchingNgModelNodes.push(ngModelNode)
}
}
- return matchingNgModelNodes;
+ return matchingNgModelNodes
}
function nodeMatches(node, selector) {
- const div = document.createElement('div');
- div.innerHTML = node.outerHTML;
- return div.querySelector(selector);
+ const div = document.createElement('div')
+ div.innerHTML = node.outerHTML
+ return div.querySelector(selector)
}
function getBuiltInAttributes() {
const ngModelAttributes = {
focus: {
- attribute: 'formly-focus'
- }
- };
- const boundOnly = [];
- const bothBooleanAndBound = ['required', 'disabled'];
- const bothAttributeAndBound = ['pattern', 'minlength'];
- const statementOnly = ['change', 'keydown', 'keyup', 'keypress', 'click', 'focus', 'blur'];
- const attributeOnly = ['placeholder', 'min', 'max', 'tabindex', 'type'];
+ attribute: 'formly-focus',
+ },
+ }
+ const boundOnly = []
+ const bothBooleanAndBound = ['required', 'disabled']
+ const bothAttributeAndBound = ['pattern', 'minlength']
+ const statementOnly = ['change', 'keydown', 'keyup', 'keypress', 'click', 'focus', 'blur']
+ const attributeOnly = ['placeholder', 'min', 'max', 'step', 'tabindex', 'type']
if (formlyConfig.extras.ngModelAttrsManipulatorPreferUnbound) {
- bothAttributeAndBound.push('maxlength');
+ bothAttributeAndBound.push('maxlength')
} else {
- boundOnly.push('maxlength');
+ boundOnly.push('maxlength')
}
angular.forEach(boundOnly, item => {
- ngModelAttributes[item] = {bound: 'ng-' + item};
- });
+ ngModelAttributes[item] = {bound: 'ng-' + item}
+ })
angular.forEach(bothBooleanAndBound, item => {
- ngModelAttributes[item] = {boolean: item, bound: 'ng-' + item};
- });
+ ngModelAttributes[item] = {boolean: item, bound: 'ng-' + item}
+ })
angular.forEach(bothAttributeAndBound, item => {
- ngModelAttributes[item] = {attribute: item, bound: 'ng-' + item};
- });
+ ngModelAttributes[item] = {attribute: item, bound: 'ng-' + item}
+ })
angular.forEach(statementOnly, item => {
- const propName = 'on' + item.substr(0, 1).toUpperCase() + item.substr(1);
- ngModelAttributes[propName] = {statement: 'ng-' + item};
- });
+ const propName = 'on' + item.substr(0, 1).toUpperCase() + item.substr(1)
+ ngModelAttributes[propName] = {statement: 'ng-' + item}
+ })
angular.forEach(attributeOnly, item => {
- ngModelAttributes[item] = {attribute: item};
- });
- return ngModelAttributes;
+ ngModelAttributes[item] = {attribute: item}
+ })
+ return ngModelAttributes
}
function getEpValue(ep, name) {
return ep['templateOptions.' + name] ||
ep[`templateOptions['${name}']`] ||
- ep[`templateOptions["${name}"]`];
+ ep[`templateOptions["${name}"]`]
}
function addIfNotPresent(nodes, attr, val) {
angular.forEach(nodes, node => {
if (!node.getAttribute(attr)) {
- node.setAttribute(attr, val);
+ node.setAttribute(attr, val)
}
- });
+ })
}
function addRegardlessOfPresence(nodes, attr, val) {
angular.forEach(nodes, node => {
- node.setAttribute(attr, val);
- });
+ node.setAttribute(attr, val)
+ })
}
function isPropertyAccessor(key) {
- return contains(key, '.') || (contains(key, '[') && contains(key, ']'));
+ return contains(key, '.') || (contains(key, '[') && contains(key, ']'))
}
}
diff --git a/src/run/formlyNgModelAttrsManipulator.test.js b/src/run/formlyNgModelAttrsManipulator.test.js
index 008a44b0..b4f0fc3b 100644
--- a/src/run/formlyNgModelAttrsManipulator.test.js
+++ b/src/run/formlyNgModelAttrsManipulator.test.js
@@ -1,375 +1,375 @@
/* eslint max-len:0 */
-import angular from 'angular';
-import _ from 'lodash';
+import angular from 'angular'
+import _ from 'lodash'
describe('formlyNgModelAttrsManipulator', () => {
- beforeEach(window.module('formly'));
+ beforeEach(window.module('formly'))
- let formlyConfig, manipulator, scope, field, result, resultEl, resultNode;
- const template = ' ';
+ let formlyConfig, manipulator, scope, field, result, resultEl, resultNode
+ const template = ' '
beforeEach(inject((_formlyConfig_, $rootScope) => {
- formlyConfig = _formlyConfig_;
- manipulator = formlyConfig.templateManipulators.preWrapper[0];
- scope = $rootScope.$new();
- scope.id = 'id';
+ formlyConfig = _formlyConfig_
+ manipulator = formlyConfig.templateManipulators.preWrapper[0]
+ scope = $rootScope.$new()
+ scope.id = 'id'
field = {
extras: {},
data: {},
validation: {},
- templateOptions: {}
- };
- }));
+ templateOptions: {},
+ }
+ }))
describe(`skipping`, () => {
it(`should allow you to skip the manipulator wholesale for the field`, () => {
- field.extras.skipNgModelAttrsManipulator = true;
- manipulate();
- expect(result).to.equal(template);
- });
+ field.extras.skipNgModelAttrsManipulator = true
+ manipulate()
+ expect(result).to.equal(template)
+ })
- const skipWithSelectorTitle = `should allow you to specify a selector for specific elements to skip`;
+ const skipWithSelectorTitle = `should allow you to specify a selector for specific elements to skip`
function skipWithSelector() {
- const className = 'ignored-thing' + _.random(0, 10);
- field.templateOptions.required = true;
- field.extras.skipNgModelAttrsManipulator = `.${className}`;
+ const className = 'ignored-thing' + _.random(0, 10)
+ field.templateOptions.required = true
+ field.extras.skipNgModelAttrsManipulator = `.${className}`
manipulate(`
- `);
- const firstInput = angular.element(resultNode.querySelector('.first-thing'));
- const secondInput = angular.element(resultNode.querySelector(`.${className}`));
- expect(firstInput.attr('required')).to.exist;
- expect(secondInput.attr('required')).to.not.exist;
+ `)
+ const firstInput = angular.element(resultNode.querySelector('.first-thing'))
+ const secondInput = angular.element(resultNode.querySelector(`.${className}`))
+ expect(firstInput.attr('required')).to.exist
+ expect(secondInput.attr('required')).to.not.exist
}
- it(skipWithSelectorTitle, skipWithSelector);
+ it(skipWithSelectorTitle, skipWithSelector)
- const skipWithAttributeTitle = `should allow you to place the attribute formly-skip-ng-model-attrs-manipulator on an ng-model to have it skip`;
+ const skipWithAttributeTitle = `should allow you to place the attribute formly-skip-ng-model-attrs-manipulator on an ng-model to have it skip`
function skipWithAttribute() {
- field.templateOptions.required = true;
+ field.templateOptions.required = true
manipulate(`
- `);
- const firstInput = angular.element(resultNode.querySelector('.first-thing'));
- const secondInput = angular.element(resultNode.querySelector('[formly-skip-ng-model-attrs-manipulator]'));
- expect(firstInput.attr('required')).to.exist;
- expect(secondInput.attr('required')).to.not.exist;
+ `)
+ const firstInput = angular.element(resultNode.querySelector('.first-thing'))
+ const secondInput = angular.element(resultNode.querySelector('[formly-skip-ng-model-attrs-manipulator]'))
+ expect(firstInput.attr('required')).to.exist
+ expect(secondInput.attr('required')).to.not.exist
}
- it(skipWithAttributeTitle, skipWithAttribute);
+ it(skipWithAttributeTitle, skipWithAttribute)
- const dontSkipWithBooleanTitle = `should not skip by selector if skipNgModelAttrsManipulator is a boolean value`;
+ const dontSkipWithBooleanTitle = `should not skip by selector if skipNgModelAttrsManipulator is a boolean value`
function dontSkipWithBoolean() {
- field.templateOptions.required = true;
- field.extras.skipNgModelAttrsManipulator = false;
+ field.templateOptions.required = true
+ field.extras.skipNgModelAttrsManipulator = false
manipulate(`
- `);
- const firstInput = angular.element(resultNode.querySelector('.first-thing'));
- const secondInput = angular.element(resultNode.querySelector('.second-thing'));
- expect(firstInput.attr('required')).to.exist;
- expect(secondInput.attr('required')).to.exist;
+ `)
+ const firstInput = angular.element(resultNode.querySelector('.first-thing'))
+ const secondInput = angular.element(resultNode.querySelector('.second-thing'))
+ expect(firstInput.attr('required')).to.exist
+ expect(secondInput.attr('required')).to.exist
}
- it(dontSkipWithBooleanTitle, dontSkipWithBoolean);
+ it(dontSkipWithBooleanTitle, dontSkipWithBoolean)
- const skipWithAttributeAndSelectorTitle = `should allow you to skip using both the special attribute and the custom selector`;
+ const skipWithAttributeAndSelectorTitle = `should allow you to skip using both the special attribute and the custom selector`
function skipWithAttributeAndSelector() {
- const className = 'ignored-thing' + _.random(0, 10);
- field.templateOptions.required = true;
- field.extras.skipNgModelAttrsManipulator = `.${className}`;
+ const className = 'ignored-thing' + _.random(0, 10)
+ field.templateOptions.required = true
+ field.extras.skipNgModelAttrsManipulator = `.${className}`
manipulate(`
- `);
- const firstInput = angular.element(resultNode.querySelector('.first-thing'));
- const secondInput = angular.element(resultNode.querySelector(`.${className}`));
- const thirdInput = angular.element(resultNode.querySelector('[formly-skip-ng-model-attrs-manipulator]'));
- expect(firstInput.attr('required')).to.exist;
- expect(secondInput.attr('required')).to.not.exist;
- expect(thirdInput.attr('required')).to.not.exist;
+ `)
+ const firstInput = angular.element(resultNode.querySelector('.first-thing'))
+ const secondInput = angular.element(resultNode.querySelector(`.${className}`))
+ const thirdInput = angular.element(resultNode.querySelector('[formly-skip-ng-model-attrs-manipulator]'))
+ expect(firstInput.attr('required')).to.exist
+ expect(secondInput.attr('required')).to.not.exist
+ expect(thirdInput.attr('required')).to.not.exist
}
- it(skipWithAttributeAndSelectorTitle, skipWithAttributeAndSelector);
+ it(skipWithAttributeAndSelectorTitle, skipWithAttributeAndSelector)
//repeat a few skipping tests with a broken Element.querySelectorAll function
describe('node search fallback', () => {
- let origQuerySelectorAll;
+ let origQuerySelectorAll
//deliberately break querySelectorAll to mimic IE8's behaviour
beforeEach(() => {
- origQuerySelectorAll = Element.prototype.querySelectorAll;
+ origQuerySelectorAll = Element.prototype.querySelectorAll
Element.prototype.querySelectorAll = function brokenQuerySelectorAll(selector) {
if (selector && selector.indexOf(':not') >= 0) {
- throw new Error(':not selector not supported');
+ throw new Error(':not selector not supported')
}
- return origQuerySelectorAll.apply(this, arguments);
- };
- });
+ return origQuerySelectorAll.apply(this, arguments)
+ }
+ })
afterEach(() => {
- Element.prototype.querySelectorAll = origQuerySelectorAll;
- });
+ Element.prototype.querySelectorAll = origQuerySelectorAll
+ })
- it(skipWithSelectorTitle, skipWithSelector);
- it(skipWithAttributeTitle, skipWithAttribute);
- it(dontSkipWithBooleanTitle, dontSkipWithBoolean);
- it(skipWithAttributeAndSelectorTitle, skipWithAttributeAndSelector);
- });
+ it(skipWithSelectorTitle, skipWithSelector)
+ it(skipWithAttributeTitle, skipWithAttribute)
+ it(dontSkipWithBooleanTitle, dontSkipWithBoolean)
+ it(skipWithAttributeAndSelectorTitle, skipWithAttributeAndSelector)
+ })
- });
+ })
it(`should have a limited number of automatically added attributes without any specific options`, () => {
- manipulate();
+ manipulate()
// because different browsers place attributes in different places...
- const spaces = ' '.split(' ').length;
- expect(result.split(' ').length).to.equal(spaces);
- attrExists('ng-model');
- attrExists('id');
- attrExists('name');
- });
+ const spaces = ' '.split(' ').length
+ expect(result.split(' ').length).to.equal(spaces)
+ attrExists('ng-model')
+ attrExists('id')
+ attrExists('name')
+ })
it(`should automatically add an id and name`, () => {
- manipulate();
- expect(resultEl.attr('name')).to.eq('id');
- expect(resultEl.attr('id')).to.eq('id');
- });
+ manipulate()
+ expect(resultEl.attr('name')).to.eq('id')
+ expect(resultEl.attr('id')).to.eq('id')
+ })
describe(`name`, () => {
it(`should automatically be added when id is specified`, () => {
- scope.id = 'some_random_id';
- manipulate();
- expect(resultEl.attr('name')).to.eq('some_random_id');
- expect(resultEl.attr('id')).to.eq('some_random_id');
- });
+ scope.id = 'some_random_id'
+ manipulate()
+ expect(resultEl.attr('name')).to.eq('some_random_id')
+ expect(resultEl.attr('id')).to.eq('some_random_id')
+ })
it(`should allow to be set in scope`, () => {
- scope.id = 'some_random_id';
- scope.name = 'some_random_name';
- manipulate();
- expect(resultEl.attr('name')).to.eq('some_random_name');
- expect(resultEl.attr('id')).to.eq('some_random_id');
- });
- });
+ scope.id = 'some_random_id'
+ scope.name = 'some_random_name'
+ manipulate()
+ expect(resultEl.attr('name')).to.eq('some_random_name')
+ expect(resultEl.attr('id')).to.eq('some_random_id')
+ })
+ })
describe(`ng-model-options`, () => {
it(`should be added if modelOptions is specified`, () => {
- field.modelOptions = {};
- manipulate();
- attrExists('ng-model-options');
- });
+ field.modelOptions = {}
+ manipulate()
+ attrExists('ng-model-options')
+ })
it(`should change the value of ng-model if getterSetter is specified`, () => {
- field.modelOptions = {getterSetter: true};
- manipulate();
- expect(resultEl.attr('ng-model')).to.equal('options.value');
- });
- });
+ field.modelOptions = {getterSetter: true}
+ manipulate()
+ expect(resultEl.attr('ng-model')).to.equal('options.value')
+ })
+ })
describe(`selector key notation`, () => {
it(`should change the ng-model when the key is a dot property accessor`, () => {
- field.key = 'bar.foo';
- manipulate();
- expect(resultEl.attr('ng-model')).to.equal('model.' + field.key);
- });
+ field.key = 'bar.foo'
+ manipulate()
+ expect(resultEl.attr('ng-model')).to.equal('model.' + field.key)
+ })
it(`should change the ng-model when the key is a bracket property accessor`, () => {
- field.key = 'bar["foo-bar"]';
- manipulate();
- expect(resultEl.attr('ng-model')).to.equal('model.' + field.key);
- });
- });
+ field.key = 'bar["foo-bar"]'
+ manipulate()
+ expect(resultEl.attr('ng-model')).to.equal('model.' + field.key)
+ })
+ })
describe(`formly-custom-validation`, () => {
it(`shouldn't be added if there aren't validators or messages`, () => {
- formlyCustomValidationPresence(false);
- });
+ formlyCustomValidationPresence(false)
+ })
it(`should be added if there are validators`, () => {
- field.validators = {foo: 'bar'};
- formlyCustomValidationPresence(true);
- });
+ field.validators = {foo: 'bar'}
+ formlyCustomValidationPresence(true)
+ })
it(`should be added if there are messages`, () => {
- field.validators = {foo: 'bar'};
- field.validation.messages = {foo: '"bar"'};
- formlyCustomValidationPresence(true);
- });
+ field.validators = {foo: 'bar'}
+ field.validation.messages = {foo: '"bar"'}
+ formlyCustomValidationPresence(true)
+ })
it(`should be added if there are validators and messages`, () => {
- field.validators = {foo: 'bar'};
- field.validation.messages = {foo: '"bar"'};
- formlyCustomValidationPresence(true);
- });
+ field.validators = {foo: 'bar'}
+ field.validation.messages = {foo: '"bar"'}
+ formlyCustomValidationPresence(true)
+ })
function formlyCustomValidationPresence(present) {
- manipulate();
- attrExists('formly-custom-validation', !present);
+ manipulate()
+ attrExists('formly-custom-validation', !present)
}
- });
+ })
describe(`templateOptions attributes`, () => {
describe(`boolean attributes`, () => {
- testAttribute('required');
- testAttribute('disabled');
+ testAttribute('required')
+ testAttribute('disabled')
function testAttribute(name) {
it(`should allow you to specify 'true' for ${name}`, () => {
field.templateOptions = {
- [name]: true
- };
- manipulate();
- attrExists(name);
- });
+ [name]: true,
+ }
+ manipulate()
+ attrExists(name)
+ })
it(`should allow you to specify 'false' for ${name}`, () => {
field.templateOptions = {
- [name]: false
- };
- manipulate();
- attrExists(name, false);
- attrExists(`ng-${name}`, false);
- });
+ [name]: false,
+ }
+ manipulate()
+ attrExists(name, false)
+ attrExists(`ng-${name}`, false)
+ })
it(`should allow you to specify expressionProperties for ${name}`, () => {
field.expressionProperties = {
- [`templateOptions.${name}`]: 'someExpression'
- };
- manipulate();
- attrExists(name, false);
- attrExists(`ng-${name}`);
- expect(resultEl.attr(`ng-${name}`)).to.eq(`options.templateOptions['${name}']`);
- });
+ [`templateOptions.${name}`]: 'someExpression',
+ }
+ manipulate()
+ attrExists(name, false)
+ attrExists(`ng-${name}`)
+ expect(resultEl.attr(`ng-${name}`)).to.eq(`options.templateOptions['${name}']`)
+ })
}
- });
+ })
describe(`attributeOnly`, () => {
- ['placeholder', 'min', 'max', 'tabindex', 'type'].forEach(testAttribute);
+ ['placeholder', 'min', 'max', 'step', 'tabindex', 'type'].forEach(testAttribute)
function testAttribute(name) {
it(`should be placed as an attribute if it is present in the templateOptions`, () => {
field.templateOptions = {
- [name]: 'Ammon'
- };
- manipulate();
- expect(resultEl.attr(name)).to.eq('Ammon');
- });
+ [name]: 'Ammon',
+ }
+ manipulate()
+ expect(resultEl.attr(name)).to.eq('Ammon')
+ })
it(`should be placed as an attribute with {{expression}} if it is present in the expressionProperties`, () => {
field.expressionProperties = {
- ['templateOptions.' + name]: 'Ammon'
- };
- manipulate();
- expect(resultEl.attr(name)).to.eq(`{{options.templateOptions['${name}']}}`);
- });
+ ['templateOptions.' + name]: 'Ammon',
+ }
+ manipulate()
+ expect(resultEl.attr(name)).to.eq(`{{options.templateOptions['${name}']}}`)
+ })
}
- });
+ })
describe(`preferUnbound`, () => {
it(`should prefer to specify maxlength as ng-maxlegnth even when it's not in expressionProperties`, () => {
field.templateOptions = {
- maxlength: 3
- };
- manipulate();
- expect(resultEl.attr('ng-maxlength')).to.eq(`options.templateOptions['maxlength']`);
- attrExists('maxlength', false);
- });
+ maxlength: 3,
+ }
+ manipulate()
+ expect(resultEl.attr('ng-maxlength')).to.eq(`options.templateOptions['maxlength']`)
+ attrExists('maxlength', false)
+ })
it(`should allow you to specify maxlength that gets set to maxlength if it's not in expressionProperties`, () => {
- formlyConfig.extras.ngModelAttrsManipulatorPreferUnbound = true;
+ formlyConfig.extras.ngModelAttrsManipulatorPreferUnbound = true
field.templateOptions = {
- maxlength: 3
- };
- manipulate();
- attrExists('ng-maxlength', false);
- expect(resultEl.attr('maxlength')).to.eq('3');
- formlyConfig.extras.ngModelAttrsManipulatorPreferUnbound = false;
- });
+ maxlength: 3,
+ }
+ manipulate()
+ attrExists('ng-maxlength', false)
+ expect(resultEl.attr('maxlength')).to.eq('3')
+ formlyConfig.extras.ngModelAttrsManipulatorPreferUnbound = false
+ })
it(`should still allow maxlength work with expressionProperties`, () => {
field.expressionProperties = {
- 'templateOptions.maxlength': '3'
- };
- manipulate();
- expect(resultEl.attr('ng-maxlength')).to.eq(`options.templateOptions['maxlength']`);
- attrExists('maxlength', false);
- });
+ 'templateOptions.maxlength': '3',
+ }
+ manipulate()
+ expect(resultEl.attr('ng-maxlength')).to.eq(`options.templateOptions['maxlength']`)
+ attrExists('maxlength', false)
+ })
- });
- });
+ })
+ })
describe(`ngModelElAttrs`, () => {
it(`should place the attributes you specify on the ng-model element`, () => {
_.assign(field, {
- ngModelElAttrs: {foo: '{{::to.bar}}'}
- });
- manipulate();
+ ngModelElAttrs: {foo: '{{::to.bar}}'},
+ })
+ manipulate()
expect(
resultNode.getAttribute('foo'),
'foo attribute should equal the value of foo in ngModelElAttrs'
- ).to.equal('{{::to.bar}}');
- });
+ ).to.equal('{{::to.bar}}')
+ })
it(`should work with multiple ng-models`, () => {
_.assign(field, {
- ngModelElAttrs: {foo: '{{::to.bar}}'}
- });
+ ngModelElAttrs: {foo: '{{::to.bar}}'},
+ })
manipulate(`
- `);
+ `)
expect(
resultNode.querySelector('.first').getAttribute('foo'),
'foo attribute should equal the value of foo in ngModelElAttrs'
- ).to.equal('{{::to.bar}}');
+ ).to.equal('{{::to.bar}}')
expect(
resultNode.querySelector('.second').getAttribute('foo'),
'foo attribute should equal the value of foo in ngModelElAttrs'
- ).to.equal('{{::to.bar}}');
+ ).to.equal('{{::to.bar}}')
- });
+ })
it(`should override existing attributes`, () => {
field.ngModelElAttrs = {
- 'ng-model': 'formState.foo.bar'
- };
- manipulate();
- expect(resultEl.attr('ng-model')).to.equal('formState.foo.bar');
- });
- });
+ 'ng-model': 'formState.foo.bar',
+ }
+ manipulate()
+ expect(resultEl.attr('ng-model')).to.equal('formState.foo.bar')
+ })
+ })
function manipulate(theTemplate = template) {
- result = manipulator(theTemplate, field, scope);
- resultEl = angular.element(result);
- resultNode = resultEl[0];
+ result = manipulator(theTemplate, field, scope)
+ resultEl = angular.element(result)
+ resultNode = resultEl[0]
}
function attrExists(name, notExists) {
- const attr = resultNode.getAttribute(name);
+ const attr = resultNode.getAttribute(name)
if (notExists) {
- expect(attr).to.be.null;
+ expect(attr).to.be.null
} else {
- expect(attr).to.be.defined;
+ expect(attr).to.be.defined
}
}
-});
+})
diff --git a/src/services/formlyUtil.js b/src/services/formlyUtil.js
index 246e6c49..4ce02f5e 100644
--- a/src/services/formlyUtil.js
+++ b/src/services/formlyUtil.js
@@ -1,8 +1,8 @@
-import utils from '../other/utils';
+import utils from '../other/utils'
-export default formlyUtil;
+export default formlyUtil
// @ngInject
function formlyUtil() {
- return utils;
+ return utils
}
diff --git a/src/services/formlyUtil.test.js b/src/services/formlyUtil.test.js
index ccec71c6..e44ee7bf 100644
--- a/src/services/formlyUtil.test.js
+++ b/src/services/formlyUtil.test.js
@@ -1,12 +1,12 @@
describe('formlyUtil', () => {
- beforeEach(window.module('formly'));
+ beforeEach(window.module('formly'))
describe('reverseDeepMerge', () => {
- let merge;
+ let merge
beforeEach(inject(function(formlyUtil) {
- merge = formlyUtil.reverseDeepMerge;
- }));
+ merge = formlyUtil.reverseDeepMerge
+ }))
it(`should modify and prefer the first object`, () => {
const firstObj = {
@@ -14,13 +14,13 @@ describe('formlyUtil', () => {
obj2a: {
string3a: 'Hello world',
number3a: 4,
- bool3a: false
- }
+ bool3a: false,
+ },
},
arry1a: [
- 1, 2, 3, 4
- ]
- };
+ 1, 2, 3, 4,
+ ],
+ }
const secondObj = {
obj1a: {
obj2a: {
@@ -28,17 +28,17 @@ describe('formlyUtil', () => {
string3b: 'Should exist',
number3a: 5,
bool3a: true,
- bool3b: false
- }
- }
- };
+ bool3b: false,
+ },
+ },
+ }
const thirdObj = {
obj1a: 'false',
arry1a: [
- 4, 3, 2, 1, 0
- ]
- };
+ 4, 3, 2, 1, 0,
+ ],
+ }
const result = {
obj1a: {
@@ -47,77 +47,77 @@ describe('formlyUtil', () => {
string3b: 'Should exist',
number3a: 4,
bool3a: false,
- bool3b: false
- }
+ bool3b: false,
+ },
},
arry1a: [
- 1, 2, 3, 4, 0
- ]
- };
+ 1, 2, 3, 4, 0,
+ ],
+ }
- merge(firstObj, secondObj, thirdObj);
- expect(firstObj).to.eql(result);
- });
+ merge(firstObj, secondObj, thirdObj)
+ expect(firstObj).to.eql(result)
+ })
it(`should allow for adding of empty objects`, () => {
const firstObj = {
a: 'a',
- b: 'b'
- };
+ b: 'b',
+ }
const secondObj = {
data: {},
templateOptions: {},
- validation: {}
- };
+ validation: {},
+ }
const result = {
a: 'a',
b: 'b',
data: {},
templateOptions: {},
- validation: {}
- };
+ validation: {},
+ }
- merge(firstObj, secondObj);
- expect(firstObj).to.eql(result);
- });
- });
+ merge(firstObj, secondObj)
+ expect(firstObj).to.eql(result)
+ })
+ })
describe('findByNodeName', () => {
- let find, $compile, scope;
+ let find, $compile, scope
beforeEach(inject(function(_$compile_, $rootScope, formlyUtil) {
- $compile = _$compile_;
- scope = $rootScope;
- find = formlyUtil.findByNodeName;
- }));
+ $compile = _$compile_
+ scope = $rootScope
+ find = formlyUtil.findByNodeName
+ }))
it('should find an element by nodeName from a single root', () => {
const template =
- '
';
- const el = $compile(template)(scope);
- const found = find(el, 'input');
- expect(found.length).to.equal(1);
- expect(found.prop('nodeName')).to.equal('INPUT');
- });
+ '
'
+ const el = $compile(template)(scope)
+ const found = find(el, 'input')
+ expect(found.length).to.equal(1)
+ expect(found.prop('nodeName')).to.equal('INPUT')
+ })
it('should find an element by nodeName from multiple root', () => {
const template =
'
' +
- ' ';
- const el = $compile(template)(scope);
- const found = find(el, 'i');
- expect(found.length).to.equal(1);
- expect(found.prop('nodeName')).to.equal('I');
- });
+ ' '
+ const el = $compile(template)(scope)
+ const found = find(el, 'i')
+ expect(found.length).to.equal(1)
+ expect(found.prop('nodeName')).to.equal('I')
+ })
it('should return undefined when a node can\'t be found', () => {
const template =
- '
';
- const el = $compile(template)(scope);
- const found = find(el, 'bla');
- expect(found).to.be.undefined;
- });
+ '
'
+ const el = $compile(template)(scope)
+ const found = find(el, 'bla')
+ expect(found).to.be.undefined
+ })
- });
-});
+ })
+})
diff --git a/src/services/formlyWarn.js b/src/services/formlyWarn.js
index 40c5c86d..4c33c876 100644
--- a/src/services/formlyWarn.js
+++ b/src/services/formlyWarn.js
@@ -1,14 +1,14 @@
-export default formlyWarn;
+export default formlyWarn
// @ngInject
function formlyWarn(formlyConfig, formlyErrorAndWarningsUrlPrefix, $log) {
return function warn() {
if (!formlyConfig.disableWarnings) {
- const args = Array.prototype.slice.call(arguments);
- const warnInfoSlug = args.shift();
- args.unshift('Formly Warning:');
- args.push(`${formlyErrorAndWarningsUrlPrefix}${warnInfoSlug}`);
- $log.warn(...args);
+ const args = Array.prototype.slice.call(arguments)
+ const warnInfoSlug = args.shift()
+ args.unshift('Formly Warning:')
+ args.push(`${formlyErrorAndWarningsUrlPrefix}${warnInfoSlug}`)
+ $log.warn(...args)
}
- };
+ }
}
diff --git a/src/test.utils.js b/src/test.utils.js
index 76db583c..94629e3a 100644
--- a/src/test.utils.js
+++ b/src/test.utils.js
@@ -1,60 +1,60 @@
/* eslint no-console:0 */
-import _ from 'lodash';
+import _ from 'lodash'
-let key = 0;
+let key = 0
-const input = ' ';
+const input = ' '
const multiNgModelField = `
-`;
-const basicForm = ' ';
+`
+const basicForm = ' '
export default {
- getNewField, input, multiNgModelField, basicForm, shouldWarn, shouldNotWarn, shouldWarnWithLog
-};
+ getNewField, input, multiNgModelField, basicForm, shouldWarn, shouldNotWarn, shouldWarnWithLog,
+}
function getNewField(options) {
- return _.merge({template: input, key: key++}, options);
+ return _.merge({template: input, key: key++}, options)
}
function shouldWarn(match, test) {
- const originalWarn = console.warn;
- let calledArgs;
+ const originalWarn = console.warn
+ let calledArgs
console.warn = function() {
- calledArgs = arguments;
- };
- test();
- expect(calledArgs, 'expected warning and there was none').to.exist;
- expect(Array.prototype.join.call(calledArgs, ' ')).to.match(match);
- console.warn = originalWarn;
+ calledArgs = arguments
+ }
+ test()
+ expect(calledArgs, 'expected warning and there was none').to.exist
+ expect(Array.prototype.join.call(calledArgs, ' ')).to.match(match)
+ console.warn = originalWarn
}
function shouldNotWarn(test) {
- const originalWarn = console.warn;
- let calledArgs;
+ const originalWarn = console.warn
+ let calledArgs
console.warn = function() {
- calledArgs = arguments;
- };
- test();
+ calledArgs = arguments
+ }
+ test()
if (calledArgs) {
- console.log(calledArgs);
- throw new Error('Expected no warning, but there was one', calledArgs);
+ console.log(calledArgs)
+ throw new Error('Expected no warning, but there was one', calledArgs)
}
- console.warn = originalWarn;
+ console.warn = originalWarn
}
function shouldWarnWithLog($log, logArgs, test) {
/* eslint no-console:0 */
- test();
- expect($log.warn.logs, '$log should have only been called once').to.have.length(1);
- const log = $log.warn.logs[0];
+ test()
+ expect($log.warn.logs, '$log should have only been called once').to.have.length(1)
+ const log = $log.warn.logs[0]
_.each(logArgs, (arg, index) => {
if (_.isRegExp(arg)) {
- expect(log[index]).to.match(arg);
+ expect(log[index]).to.match(arg)
} else {
- expect(log[index]).to.equal(arg);
+ expect(log[index]).to.equal(arg)
}
- });
+ })
}