From 347be5ae9aa6829427e1e8e1b1e58afdf2a36c0a Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 13 Jan 2011 10:35:26 -0800 Subject: fixed select with ng:format select (one/multiple) could not chose from a list of objects, since DOM requires string ids. Solved by adding index formatter, which exposed incorrect handling of formatters in select widgets. --- src/JSON.js | 2 +- src/directives.js | 8 +-- src/formatters.js | 62 ++++++++++++++++++++++- src/parser.js | 31 ++++++++++++ src/widgets.js | 148 ++++++++++++++++++++++++++++++++---------------------- 5 files changed, 185 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/JSON.js b/src/JSON.js index 0d23314f..9a2b34e5 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -33,7 +33,7 @@ function toJson(obj, pretty) { * @returns {Object|Array|Date|string|number} Deserialized thingy. */ function fromJson(json, useNative) { - if (!json) return json; + if (!isString(json)) return json; var obj, p, expression; diff --git a/src/directives.js b/src/directives.js index 8584df8b..10d1f1e5 100644 --- a/src/directives.js +++ b/src/directives.js @@ -197,7 +197,7 @@ angularDirective("ng:bind", function(expression, element){ if (lastValue === value && lastError == error) return; isDomElement = isElement(value); if (!isHtml && !isDomElement && isObject(value)) { - value = toJson(value); + value = toJson(value, true); } if (value != lastValue || error != lastError) { lastValue = value; @@ -234,7 +234,7 @@ function compileBindTemplate(template){ return text; }); }); - bindTemplateCache[template] = fn = function(element){ + bindTemplateCache[template] = fn = function(element, prettyPrintJson){ var parts = [], self = this, oldElement = this.hasOwnProperty($$element) ? self.$element : _undefined; self.$element = element; @@ -243,7 +243,7 @@ function compileBindTemplate(template){ if (isElement(value)) value = ''; else if (isObject(value)) - value = toJson(value, true); + value = toJson(value, prettyPrintJson); parts.push(value); } self.$element = oldElement; @@ -292,7 +292,7 @@ angularDirective("ng:bind-template", function(expression, element){ return function(element) { var lastValue; this.$onEval(function() { - var value = templateFn.call(this, element); + var value = templateFn.call(this, element, true); if (value != lastValue) { element.text(value); lastValue = value; diff --git a/src/formatters.js b/src/formatters.js index 19b8df81..5e49ccf4 100644 --- a/src/formatters.js +++ b/src/formatters.js @@ -15,7 +15,7 @@ angularFormatter.noop = formatter(identity, identity); * @description * Formats the user input as JSON text. * - * @returns {string} A JSON string representation of the model. + * @returns {?string} A JSON string representation of the model. * * @example *
@@ -30,7 +30,9 @@ angularFormatter.noop = formatter(identity, identity); * expect(binding('data')).toEqual('data={\n }'); * }); */ -angularFormatter.json = formatter(toJson, fromJson); +angularFormatter.json = formatter(toJson, function(value){ + return fromJson(value || 'null'); +}); /** * @workInProgress @@ -154,3 +156,59 @@ angularFormatter.list = formatter( angularFormatter.trim = formatter( function(obj) { return obj ? trim("" + obj) : ""; } ); + +/** + * @workInProgress + * @ngdoc formatter + * @name angular.formatter.index + * @description + * Index formatter is meant to be used with `select` input widget. It is useful when one needs + * to select from a set of objects. To create pull-down one can iterate over the array of object + * to build the UI. However the value of the pull-down must be a string. This means that when on + * object is selected form the pull-down, the pull-down value is a string which needs to be + * converted back to an object. This conversion from string to on object is not possible, at best + * the converted object is a copy of the original object. To solve this issue we create a pull-down + * where the value strings are an index of the object in the array. When pull-down is selected the + * index can be used to look up the original user object. + * + * @inputType select + * @param {array} array to be used for selecting an object. + * @returns {object} object which is located at the selected position. + * + * @example + * + *
+ * User: + * + * + * user={{currentUser.name}}
+ * password={{currentUser.password}}
+ *
+ * + * @scenario + * it('should format trim', function(){ + * expect(binding('currentUser.password')).toEqual('guest'); + * select('currentUser').option('2'); + * expect(binding('currentUser.password')).toEqual('abc'); + * }); + */ +angularFormatter.index = formatter( + function(object, array){ + return '' + indexOf(array || [], object); + }, + function(index, array){ + return (array||[])[index]; + } +); diff --git a/src/parser.js b/src/parser.js index 4227a6c8..ac62fb97 100644 --- a/src/parser.js +++ b/src/parser.js @@ -218,6 +218,7 @@ function parser(text, json){ var ZERO = valueFn(0), tokens = lex(text, json), assignment = _assignment, + assignable = logicalOR, functionCall = _functionCall, fieldAccess = _fieldAccess, objectIndex = _objectIndex, @@ -231,6 +232,7 @@ function parser(text, json){ functionCall = fieldAccess = objectIndex = + assignable = filterChain = functionIdent = pipeFunction = @@ -238,9 +240,11 @@ function parser(text, json){ } return { assertAllConsumed: assertAllConsumed, + assignable: assignable, primary: primary, statements: statements, validator: validator, + formatter: formatter, filter: filter, //TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) watch: watch @@ -353,6 +357,33 @@ function parser(text, json){ return pipeFunction(angularValidator); } + function formatter(){ + var token = expect(); + var formatter = angularFormatter[token.text]; + var argFns = []; + var token; + if (!formatter) throwError('is not a valid formatter.', token); + while(true) { + if ((token = expect(':'))) { + argFns.push(expression()); + } else { + return valueFn({ + format:invokeFn(formatter.format), + parse:invokeFn(formatter.parse) + }); + } + } + function invokeFn(fn){ + return function(self, input){ + var args = [input]; + for ( var i = 0; i < argFns.length; i++) { + args.push(argFns[i](self)); + } + return fn.apply(self, args); + }; + } + } + function _pipeFunction(fnScope){ var fn = functionIdent(fnScope); var argsFn = []; diff --git a/src/widgets.js b/src/widgets.js index 473b6f1f..e7f78971 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -8,16 +8,16 @@ * standard HTML set. These widgets are bound using the name attribute * to an expression. In addition they can have `ng:validate`, `ng:required`, * `ng:format`, `ng:change` attribute to further control their behavior. - * + * * @usageContent * see example below for usage - * + * * *