Skip to content
This repository was archived by the owner on Apr 30, 2018. It is now read-only.

Commit 5a6825b

Browse files
author
Kent C. Dodds
committed
Parsers support #368
1 parent 1f1bd1e commit 5a6825b

File tree

7 files changed

+213
-3
lines changed

7 files changed

+213
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 6.21.0
2+
3+
## New Features
4+
5+
- Adding support for `parsers` [#368](/../../issues/368)
6+
17
# 6.20.1
28

39
## Bug Fixes

src/directives/formly-custom-validation.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ function formlyCustomValidation(formlyConfig, formlyUtil, $q, formlyWarn) {
8989
ctrl.$setValidity(name, false);
9090
}
9191
}).finally(() => {
92-
if (Object.keys(ctrl.$pending).length === 1) {
92+
const $pending = ctrl.$pending || {};
93+
if (Object.keys($pending).length === 1) {
9394
delete ctrl.$pending;
9495
} else {
9596
delete ctrl.$pending[name];

src/directives/formly-field.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
328328
scope.fc = scope.options.formControl; // shortcut for template authors
329329
stopWatchingShowError();
330330
addShowMessagesWatcher();
331+
addParsers();
331332
}
332333
});
333334
}
@@ -351,6 +352,76 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
351352
scope.showError = show; // shortcut for template authors
352353
});
353354
}
355+
356+
function addParsers() {
357+
// init with type's parsers
358+
let parsers = getParsersFromType(type);
359+
360+
// get optionsTypes parsers
361+
parsers = formlyUtil.extendArray(parsers, getParsersFromOptionsTypes(scope.options.optionsTypes));
362+
363+
// get field's parsers
364+
parsers = formlyUtil.extendArray(parsers, scope.options.parsers);
365+
366+
// convert parsers into formlyExpression parsers
367+
angular.forEach(parsers, (parser, index) => {
368+
parsers[index] = getFormlyExpressionParser(parser);
369+
});
370+
371+
let ngModelCtrls = scope.fc;
372+
if (!angular.isArray(ngModelCtrls)) {
373+
ngModelCtrls = [ngModelCtrls];
374+
}
375+
376+
angular.forEach(ngModelCtrls, ngModelCtrl => {
377+
ngModelCtrl.$parsers = ngModelCtrl.$parsers.concat(...parsers);
378+
});
379+
380+
function getParsersFromType(theType) {
381+
if (!theType) {
382+
return [];
383+
}
384+
if (angular.isString(theType)) {
385+
theType = formlyConfig.getType(theType, true, scope.options);
386+
}
387+
let typeParsers = [];
388+
389+
// get parsers from parent
390+
if (theType.extends) {
391+
typeParsers = formlyUtil.extendArray(typeParsers, getParsersFromType(theType.extends));
392+
}
393+
394+
// get own type's parsers
395+
typeParsers = formlyUtil.extendArray(typeParsers, getDefaultOptionsParsers(theType));
396+
397+
// get parsers from optionsTypes
398+
typeParsers = formlyUtil.extendArray(
399+
typeParsers,
400+
getParsersFromOptionsTypes(getDefaultOptionsOptionsTypes(theType))
401+
);
402+
403+
return typeParsers;
404+
}
405+
406+
function getParsersFromOptionsTypes(optionsTypes = []) {
407+
let optionsTypesParsers = [];
408+
angular.forEach(optionsTypes.reverse(), optionsTypeName => {
409+
optionsTypesParsers = formlyUtil.extendArray(optionsTypesParsers, getParsersFromType(optionsTypeName));
410+
});
411+
return optionsTypesParsers;
412+
}
413+
414+
function getFormlyExpressionParser(parser) {
415+
formlyExpressionParserFunction.originalParser = parser;
416+
return formlyExpressionParserFunction;
417+
418+
function formlyExpressionParserFunction($viewValue) {
419+
const $modelValue = scope.options.value();
420+
return formlyUtil.formlyEval(scope, parser, $modelValue, $viewValue);
421+
}
422+
}
423+
424+
}
354425
}
355426

356427
function callLinkFunctions() {
@@ -602,3 +673,17 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
602673

603674

604675
}
676+
677+
678+
// Stateless util functions
679+
function getDefaultOptionsParsers(type) {
680+
return getDefaultOptionsProperty(type, 'parsers', []);
681+
}
682+
683+
function getDefaultOptionsOptionsTypes(type) {
684+
return getDefaultOptionsProperty(type, 'optionsTypes', []);
685+
}
686+
687+
function getDefaultOptionsProperty(type, prop, defaultValue) {
688+
return type.defaultOptions && type.defaultOptions[prop] || defaultValue;
689+
}

src/directives/formly-field.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,96 @@ describe('formly-field', function() {
959959
});
960960
});
961961

962+
describe(`parsers/formatters`, () => {
963+
describe(`everything`, () => {
964+
it(`should merge the parsers and formatters in the right order`, () => {
965+
const parent1Parser1 = sinon.spy();
966+
const parent1Parser2 = sinon.spy();
967+
const parent2Parser1 = sinon.spy();
968+
const parent2Parser2 = sinon.spy();
969+
const childParser1 = sinon.spy();
970+
const childParser2 = sinon.spy();
971+
const optionType1Parser1 = sinon.spy();
972+
const optionType1Parser2 = sinon.spy();
973+
const optionType2Parser1 = sinon.spy();
974+
const optionType2Parser2 = sinon.spy();
975+
const fieldParser1 = sinon.spy();
976+
const fieldParser2 = sinon.spy();
977+
978+
formlyConfig.setType({
979+
name: 'parent1',
980+
defaultOptions: {
981+
parsers: [parent1Parser1, parent1Parser2]
982+
}
983+
});
984+
985+
formlyConfig.setType({
986+
name: 'parent2',
987+
defaultOptions: {
988+
parsers: [parent2Parser1, parent2Parser2]
989+
}
990+
});
991+
992+
formlyConfig.setType({
993+
name: 'child',
994+
template: '<input ng-model="model[options.key]" />',
995+
extends: 'parent1', // <-- note this!
996+
defaultOptions: {
997+
parsers: [childParser1, childParser2]
998+
}
999+
});
1000+
1001+
formlyConfig.setType({
1002+
name: 'optionType1',
1003+
extends: 'parent2', // <-- note this!
1004+
defaultOptions: {
1005+
parsers: [optionType1Parser1, optionType1Parser2]
1006+
}
1007+
});
1008+
1009+
formlyConfig.setType({
1010+
name: 'optionType2',
1011+
defaultOptions: {
1012+
parsers: [optionType2Parser1, optionType2Parser2]
1013+
}
1014+
});
1015+
1016+
scope.fields = [
1017+
{
1018+
type: 'child',
1019+
optionsTypes: ['optionType1', 'optionType2'],
1020+
parsers: [fieldParser1, fieldParser2]
1021+
}
1022+
];
1023+
1024+
compileAndDigest();
1025+
const ctrl = getNgModelCtrl();
1026+
const originalParsers = ctrl.$parsers.map(parser => parser.originalParser);
1027+
expect(originalParsers).to.eql([
1028+
parent1Parser1, parent1Parser2,
1029+
childParser1, childParser2,
1030+
parent2Parser1, parent2Parser2,
1031+
optionType1Parser1, optionType1Parser2,
1032+
optionType2Parser1, optionType2Parser2,
1033+
fieldParser1, fieldParser2
1034+
]);
1035+
});
1036+
1037+
it(`should handle a formlyExpression as a string`, () => {
1038+
scope.fields = [getNewField({
1039+
key: 'myKey',
1040+
parsers: ['$viewValue + options.data.extraThing'],
1041+
data: {extraThing: ' boo!'}
1042+
})];
1043+
compileAndDigest();
1044+
const ctrl = getNgModelCtrl();
1045+
expect(ctrl.$parsers).to.have.length(1);
1046+
ctrl.$setViewValue('hello!');
1047+
expect(scope.model.myKey).to.equal('hello! boo!');
1048+
});
1049+
});
1050+
});
1051+
9621052
describe(`link`, () => {
9631053
describe(`addClasses`, () => {
9641054
it(`should add the type class`, () => {
@@ -1472,6 +1562,10 @@ describe('formly-field', function() {
14721562
return getFieldNode(index).querySelector('[ng-model]');
14731563
}
14741564

1565+
function getNgModelCtrl(index = 0) {
1566+
return angular.element(getFieldNgModelNode(index)).controller('ngModel');
1567+
}
1568+
14751569
function shouldWarn(match, test) {
14761570
const originalWarn = console.warn;
14771571
let calledArgs;

src/other/utils.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import angular from 'angular-fix';
22

3-
export default {formlyEval, getFieldId, reverseDeepMerge, findByNodeName, arrayify, extendFunction};
3+
export default {formlyEval, getFieldId, reverseDeepMerge, findByNodeName, arrayify, extendFunction, extendArray};
44

55
function formlyEval(scope, expression, $modelValue, $viewValue, extraLocals) {
66
if (angular.isFunction(expression)) {
@@ -82,3 +82,23 @@ function extendFunction(...fns) {
8282
fns.forEach(fn => fn.apply(null, args));
8383
};
8484
}
85+
86+
function extendArray(primary, secondary, property) {
87+
if (property) {
88+
primary = primary[property];
89+
secondary = secondary[property];
90+
}
91+
if (secondary && primary) {
92+
angular.forEach(secondary, function(item) {
93+
if (primary.indexOf(item) === -1) {
94+
primary.push(item);
95+
}
96+
});
97+
return primary;
98+
} else if (secondary) {
99+
return secondary;
100+
} else {
101+
return primary;
102+
}
103+
}
104+

src/providers/formlyApiCheck.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ const fieldOptionsApiShape = {
122122
).optional,
123123
validators: validatorChecker.optional,
124124
asyncValidators: validatorChecker.optional,
125+
parsers: apiCheck.arrayOf(formlyExpression).optional,
125126
noFormControl: apiCheck.bool.optional,
126127
hide: apiCheck.bool.optional,
127128
hideExpression: formlyExpression.optional,

src/providers/formlyConfig.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,10 @@ function formlyConfig(formlyUsabilityProvider, formlyErrorAndWarningsUrlPrefix,
190190

191191
function getTypeHeritage(parent) {
192192
const heritage = [];
193-
let type = getType(parent);
193+
let type = parent;
194+
if (angular.isString(type)) {
195+
type = getType(parent);
196+
}
194197
parent = type.extends;
195198
while (parent) {
196199
type = getType(parent);

0 commit comments

Comments
 (0)