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.
---
CHANGELOG.md | 10 ++--
docs/spec/ngdocSpec.js | 63 +++++++++++++++++++++
docs/src/ngdoc.js | 110 +++++++++++++-----------------------
src/JSON.js | 2 +-
src/directives.js | 8 +--
src/formatters.js | 62 ++++++++++++++++++++-
src/parser.js | 31 +++++++++++
src/widgets.js | 148 +++++++++++++++++++++++++++++--------------------
test/FormattersSpec.js | 15 +++++
test/JsonSpec.js | 17 ++++--
test/ParserSpec.js | 25 +++++++++
test/directivesSpec.js | 15 +++--
test/widgetsSpec.js | 98 +++++++++++++++++++++++++-------
13 files changed, 433 insertions(+), 171 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eda438e6..9600206b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# 0.9.10 flea-whisperer (in-progress) #
+### Bug Fixes
+- html select (one/multiple) could not chose from a list of objects, since DOM requires string ids.
# 0.9.9 time-shift (2011-01-13) #
@@ -99,9 +101,9 @@
- small docs improvements (mainly docs for the $resource service)
### Breaking changes
-- Angular expressions in the view used to support regular expressions. This feature was rarely
- used and added unnecessary complexity. It not a good idea to have regexps in the view anyway,
- so we removed this support. If you had any regexp in your views, you will have to move them to
+- Angular expressions in the view used to support regular expressions. This feature was rarely
+ used and added unnecessary complexity. It not a good idea to have regexps in the view anyway,
+ so we removed this support. If you had any regexp in your views, you will have to move them to
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
@@ -120,7 +122,7 @@
- docs app UI polishing with dual scrolling and other improvements
### Bug Fixes
-- `select` widget now behaves correctly when it's `option` items are created via `ng:repeat`
+- `select` widget now behaves correctly when it's `option` items are created via `ng:repeat`
(issue #170)
- fix for async xhr cache issue #152 by adding `$browser.defer` and `$defer` service
diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js
index 63be610b..63981e90 100644
--- a/docs/spec/ngdocSpec.js
+++ b/docs/spec/ngdocSpec.js
@@ -1,4 +1,5 @@
var ngdoc = require('ngdoc.js');
+var DOM = require('dom.js').DOM;
describe('ngdoc', function(){
var Doc = ngdoc.Doc;
@@ -253,5 +254,67 @@ describe('ngdoc', function(){
});
});
});
+
+ describe('usage', function(){
+ var dom;
+
+ beforeEach(function(){
+ dom = new DOM();
+ this.addMatchers({
+ toContain: function(text) {
+ this.actual = this.actual.toString();
+ return this.actual.indexOf(text) > -1;
+ }
+ });
+ });
+
+ describe('filter', function(){
+ it('should format', function(){
+ var doc = new Doc({
+ ngdoc:'formatter',
+ shortName:'myFilter',
+ param: [
+ {name:'a'},
+ {name:'b'}
+ ]
+ });
+ doc.html_usage_filter(dom);
+ expect(dom).toContain('myFilter_expression | myFilter:b');
+ expect(dom).toContain('angular.filter.myFilter(a, b)');
+ });
+ });
+
+ describe('validator', function(){
+ it('should format', function(){
+ var doc = new Doc({
+ ngdoc:'validator',
+ shortName:'myValidator',
+ param: [
+ {name:'a'},
+ {name:'b'}
+ ]
+ });
+ doc.html_usage_validator(dom);
+ expect(dom).toContain('ng:validate="myValidator:b"');
+ expect(dom).toContain('angular.validator.myValidator(a, b)');
+ });
+ });
+
+ describe('formatter', function(){
+ it('should format', function(){
+ var doc = new Doc({
+ ngdoc:'formatter',
+ shortName:'myFormatter',
+ param: [
+ {name:'a'},
+ ]
+ });
+ doc.html_usage_formatter(dom);
+ expect(dom).toContain('ng:format="myFormatter:a"');
+ expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
+ expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
+ });
+ });
+ });
});
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js
index ac7d0bb1..cae24cc3 100644
--- a/docs/src/ngdoc.js
+++ b/docs/src/ngdoc.js
@@ -231,15 +231,7 @@ Doc.prototype = {
dom.code(function(){
dom.text(self.name);
dom.text('(');
- var first = true;
- (self.param || []).forEach(function(param){
- if (first) {
- first = false;
- } else {
- dom.text(', ');
- }
- dom.text(param.name);
- });
+ self.parameters(dom, ', ');
dom.text(');');
});
@@ -273,44 +265,17 @@ Doc.prototype = {
dom.text(self.shortName);
dom.text('_expression | ');
dom.text(self.shortName);
- var first = true;
- (self.param||[]).forEach(function(param){
- if (first) {
- first = false;
- } else {
- if (param.optional) {
- dom.tag('i', function(){
- dom.text('[:' + param.name + ']');
- });
- } else {
- dom.text(':' + param.name);
- }
- }
- });
+ self.parameters(dom, ':', true);
dom.text(' }}');
});
});
- dom.h3('In JavaScript', function(){
+ dom.h('In JavaScript', function(){
dom.tag('code', function(){
dom.text('angular.filter.');
dom.text(self.shortName);
dom.text('(');
- var first = true;
- (self.param||[]).forEach(function(param){
- if (first) {
- first = false;
- dom.text(param.name);
- } else {
- if (param.optional) {
- dom.tag('i', function(){
- dom.text('[, ' + param.name + ']');
- });
- } else {
- dom.text(', ' + param.name);
- }
- }
- });
+ self.parameters(dom, ', ');
dom.text(')');
});
});
@@ -319,32 +284,40 @@ Doc.prototype = {
self.html_usage_returns(dom);
});
},
-
+
html_usage_formatter: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
- dom.text('');
});
});
- dom.h3('In JavaScript', function(){
+ dom.h('In JavaScript', function(){
dom.code(function(){
dom.text('var userInputString = angular.formatter.');
dom.text(self.shortName);
- dom.text('.format(modelValue);');
- });
- dom.html(' ');
- dom.code(function(){
+ dom.text('.format(modelValue');
+ self.parameters(dom, ', ', false, true);
+ dom.text(');');
+ dom.text('\n');
dom.text('var modelValue = angular.formatter.');
dom.text(self.shortName);
- dom.text('.parse(userInputString);');
+ dom.text('.parse(userInputString');
+ self.parameters(dom, ', ', false, true);
+ dom.text(');');
});
});
+ self.html_usage_parameters(dom);
self.html_usage_returns(dom);
});
},
@@ -356,18 +329,7 @@ Doc.prototype = {
dom.code(function(){
dom.text('');
});
});
@@ -377,19 +339,7 @@ Doc.prototype = {
dom.text('angular.validator.');
dom.text(self.shortName);
dom.text('(');
- var first = true;
- (self.param||[]).forEach(function(param){
- if (first) {
- first = false;
- dom.text(param.name);
- } else {
- if (param.optional) {
- dom.text('[, ' + param.name + ']');
- } else {
- dom.text(', ' + param.name);
- }
- }
- });
+ self.parameters(dom, ', ');
dom.text(')');
});
});
@@ -443,8 +393,22 @@ Doc.prototype = {
},
html_usage_service: function(dom){
- }
+ },
+ parameters: function(dom, separator, skipFirst, prefix) {
+ var sep = prefix ? separator : '';
+ (this.param||[]).forEach(function(param, i){
+ if (!(skipFirst && i==0)) {
+ if (param.optional) {
+ dom.text('[' + sep + param.name + ']');
+ } else {
+ dom.text(sep + param.name);
+ }
+ }
+ sep = separator;
+ });
+ }
+
};
//////////////////////////////////////////////////////////
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
+ *
+ *