From 40f897e822cfa30af78a1b4398f4f0e6b5ab4952 Mon Sep 17 00:00:00 2001
From: Daniel
Date: Fri, 5 Apr 2013 16:55:16 +0200
Subject: [PATCH 01/19] Added to parse the same optional config argument that
format uses. Added tests, updated the API docs and user guide.
---
.../docs/datatype-numberparse.mustache | 37 +++++++
src/datatype/docs/index.mustache | 26 +++--
...datatype-numberparseconfig-source.mustache | 40 ++++++++
src/number/js/number-parse.js | 39 +++++++-
src/number/tests/unit/assets/number-tests.js | 98 +++++++++++++++----
5 files changed, 211 insertions(+), 29 deletions(-)
create mode 100644 src/datatype/docs/partials/datatype-numberparseconfig-source.mustache
diff --git a/src/datatype/docs/datatype-numberparse.mustache b/src/datatype/docs/datatype-numberparse.mustache
index 6428c9c1af9..d947c6f7232 100644
--- a/src/datatype/docs/datatype-numberparse.mustache
+++ b/src/datatype/docs/datatype-numberparse.mustache
@@ -39,3 +39,40 @@ YUI().use("datatype-number", function(Y) {
});
```
+A configuration argument can be added to deal with these numbers.
+```
+YUI().use("datatype-number", function(Y) {
+ var output = Y.Number.parse("$100", {
+ prefix:'$'
+ });
+ // output is 100
+
+ output = Y.Number.parse("20 dollars", {
+ suffix: 'dollars'
+ });
+ // output is 20
+
+ output = Y.Number.parse("3,000,000.12" ,{
+ thousandsSeparator: ','
+ });
+ // output is 3000000.12
+
+ output = Y.Number.parse(new Date("Jan 1, 2000"));
+ // output is 946713600000
+
+});
+```
+
+The following example uses the following configuration:
+
+```
+{
+ decimalSeparator: ',',
+ thousandsSeparator: '.',
+ prefix: '€',
+ suffix: '(EUR)'
+}
+```
+
+ {{>datatype-numberparseconfig-source}}
+
diff --git a/src/datatype/docs/index.mustache b/src/datatype/docs/index.mustache
index 117709f857b..f8e7807a5ee 100644
--- a/src/datatype/docs/index.mustache
+++ b/src/datatype/docs/index.mustache
@@ -1,15 +1,15 @@
- The DataType Utility is a collection of classes that provide type-conversion and
+ The DataType Utility is a collection of classes that provide type-conversion and
string-formatting convenience methods for numbers, dates, and XML documents.
-
+
{{>getting-started}}
-
+
Using the DataType utility
-
+
Dates
@@ -37,7 +37,7 @@ YUI({lang:"ko-KR"}).use("datatype-date", function(Y) {
href="{{apiDocs}}/classes/Date.html#method_format"> strftime format
specifiers:
-
+
- a: list of abbreviated weekday names, from Sunday to Saturday
- A: list of full weekday names, from Sunday to Saturday
@@ -117,7 +117,21 @@ Y.log(Y.Number.format(123123123.176,{
```
var number = Y.Number.parse("123123");
```
-
+
+An optional configuration can be added, compatible with that of `Y.Number.format`.
+If found, any prefix, suffix, thousands separators and whitespaces will be deleted,
+the decimal separator will be replaced by a dot and the resulting string parsed.
+The `decimalPlaces` property is ignored and is meant for compatibility with `format`.
+
+```
+Y.log(Y.Number.parse(" € 123.123.123,176 (EUR) ",{
+ prefix: "€",
+ thousandsSeparator: ".",
+ decimalSeparator: ",",
+ decimalPlaces: 2,
+ suffix: " (EUR)"
+}));
+```
XML
diff --git a/src/datatype/docs/partials/datatype-numberparseconfig-source.mustache b/src/datatype/docs/partials/datatype-numberparseconfig-source.mustache
new file mode 100644
index 00000000000..b026a00d242
--- /dev/null
+++ b/src/datatype/docs/partials/datatype-numberparseconfig-source.mustache
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+ The parsed number is:
+
+
+
+
diff --git a/src/number/js/number-parse.js b/src/number/js/number-parse.js
index ac861195fb5..d65913aff05 100755
--- a/src/number/js/number-parse.js
+++ b/src/number/js/number-parse.js
@@ -11,14 +11,49 @@ var LANG = Y.Lang;
Y.mix(Y.namespace("Number"), {
/**
* Converts data to type Number.
+ * If a `config` argument is used, it will strip the `data` of the prefix,
+ * the suffix and the thousands separator, if any of them are found,
+ * replace the decimal separator by a dot and parse the resulting string.
+ * Extra whitespace will be ignored.
*
* @method parse
* @param data {String | Number | Boolean} Data to convert. The following
* values return as null: null, undefined, NaN, "".
+ * @param [config] {Object} Optional configuration values, same as for [Y.Date.format](#method_format).
+ * @param [config.prefix] {HTML} String to be removed from the start, like a currency designator "$"
+ * @param [config.decimalPlaces] {Number} Ignored, it is accepted only for compatibility with [Y.Date.format](#method_format).
+ * @param [config.decimalSeparator] {HTML} Decimal separator.
+ * @param [config.thousandsSeparator] {HTML} Thousands separator.
+ * @param [config.suffix] {HTML} String to be removed from the end of the number, like " items".
* @return {Number} A number, or null.
*/
- parse: function(data) {
- var number = (data === null || data === "") ? data : +data;
+ parse: function(data, config) {
+ var number, r,
+ safeRegExp = /(\\|\[|\]|\.|\+|\*|\?|\^|\$|\(|\)|\||\{|\})/g,
+ safe = function(r) {
+ return r.replace(safeRegExp,'\\$1');
+ };
+ if (LANG.isString(data) && LANG.isObject(config)) {
+ data = data.replace(/\s+/g, '');
+ r = config.prefix;
+
+ if (r) {
+ data = data.replace(new RegExp('^(\\s*' + safe(r) + ')') , '');
+ }
+ r = config.suffix;
+ if (r) {
+ data = data.replace(new RegExp('(' + safe(r) + '\\s*)$'),'');
+ }
+ r = config.thousandsSeparator;
+ if (r) {
+ data = data.replace(new RegExp(safe(r),'g'),'');
+ }
+ r = config.decimalSeparator;
+ if (r && r !== '.') {
+ data = data.replace(new RegExp(safe(r)),'.');
+ }
+ }
+ number = (data === null || data === "") ? data : +data;
if(LANG.isNumber(number)) {
return number;
}
diff --git a/src/number/tests/unit/assets/number-tests.js b/src/number/tests/unit/assets/number-tests.js
index 95f38b2782b..b756ffb8a25 100644
--- a/src/number/tests/unit/assets/number-tests.js
+++ b/src/number/tests/unit/assets/number-tests.js
@@ -1,11 +1,11 @@
YUI.add('number-tests', function(Y) {
var ASSERT = Y.Assert,
ARRAYASSERT = Y.ArrayAssert;
-
+
var testParse = new Y.Test.Case({
name: "Number Parse Tests",
-
+
testUndefined: function() {
var number = Y.Number.parse();
ASSERT.isNull(number, "Expected null.");
@@ -24,7 +24,7 @@ YUI.add('number-tests', function(Y) {
testStrings: function() {
var number = Y.Number.parse("0");
ASSERT.areSame(0, number, "Incorrect number 0.");
-
+
number = Y.Number.parse("1");
ASSERT.areSame(1, number, "Incorrect number 1.");
@@ -43,7 +43,62 @@ YUI.add('number-tests', function(Y) {
ASSERT.areSame(-1, number, "Incorrect number -1.");
}
});
-
+
+ var testParseWithConfig = new Y.Test.Case({
+ name: "Number Parse w/ Config Tests",
+ tests: function () {
+ var i, v, values = [
+ ["1234.5", {}, 1234.5],
+ ["$1.234,50", {
+ prefix: '$',
+ decimalSeparator:',',
+ thousandsSeparator: '.'
+ }, 1234.5],
+ [" $ 1.234.567,80 ", {
+ prefix: '$',
+ decimalSeparator:',',
+ thousandsSeparator: '.'
+ }, 1234567.8],
+ [" 1.234,50 € ", {
+ suffix: '€',
+ decimalSeparator:',',
+ thousandsSeparator: '.'
+ }, 1234.5],
+ ["abc 1//234//567--89 def ", {
+ prefix: 'abc',
+ suffix: 'def',
+ decimalSeparator:'--',
+ thousandsSeparator: '//'
+ }, 1234567.89],
+ ["[ 1*234*567|89 ] ", {
+ prefix: '[',
+ suffix: ']',
+ decimalSeparator:'|',
+ thousandsSeparator: '*'
+ }, 1234567.89],
+ ["1234567.89", {
+ prefix: '[',
+ suffix: ']',
+ decimalSeparator:'|',
+ thousandsSeparator: '*'
+ }, 1234567.89],
+ ["123456789", {
+ prefix: '[',
+ suffix: ']',
+ decimalSeparator:'|',
+ thousandsSeparator: '*'
+ }, 123456789]
+
+ ];
+ for (i = 0; i < values.length; i +=1) {
+ v = values[i];
+ ASSERT.areSame(v[2],Y.Number.parse(v[0], v[1]),v.join(' - '));
+
+ }
+
+ }
+ });
+
var testFormat = new Y.Test.Case({
name: "Number Format Tests",
@@ -63,7 +118,7 @@ YUI.add('number-tests', function(Y) {
output = Y.Number.format("1");
ASSERT.areSame("1", output, "Incorrect output 1.");
-
+
output = Y.Number.format("-1");
ASSERT.areSame("-1", output, "Incorrect output -1.");
},
@@ -78,23 +133,23 @@ YUI.add('number-tests', function(Y) {
output = Y.Number.format(-1);
ASSERT.areSame("-1", output, "Incorrect output -1.");
},
-
+
testPrefix: function() {
var output = Y.Number.format(123, {prefix:"$"});
ASSERT.areSame("$123", output, "Incorrect prefix.");
-
+
output = Y.Number.format(-123, {prefix:"$"});
ASSERT.areSame("$-123", output, "Incorrect prefix neg.");
},
-
+
testSuffix: function() {
var output = Y.Number.format(123, {suffix:" items"});
ASSERT.areSame("123 items", output, "Incorrect suffix.");
-
+
output = Y.Number.format(-123, {suffix:" items"});
ASSERT.areSame("-123 items", output, "Incorrect suffix neg.");
},
-
+
testDecimalPlaces: function() {
var output = Y.Number.format(123.123456, {decimalPlaces:5});
ASSERT.areSame("123.12346", output, "Incorrect decimal rounding to 5 places.");
@@ -104,7 +159,7 @@ YUI.add('number-tests', function(Y) {
output = Y.Number.format(123.123, {decimalPlaces:5});
ASSERT.areSame("123.12300", output, "Incorrect decimal padding to 5 places.");
-
+
output = Y.Number.format(-123.123, {decimalPlaces:5});
ASSERT.areSame("-123.12300", output, "Incorrect decimal padding to 5 places neg.");
@@ -116,39 +171,39 @@ YUI.add('number-tests', function(Y) {
output = Y.Number.format(123.127, {decimalPlaces:2});
ASSERT.areSame("123.13", output, "Incorrect decimal rounding to 2 places up.");
-
+
output = Y.Number.format(-123.127, {decimalPlaces:2});
ASSERT.areSame("-123.13", output, "Incorrect decimal rounding to 2 places up neg.");
output = Y.Number.format(123.123, {decimalPlaces:2});
ASSERT.areSame("123.12", output, "Incorrect decimal rounding to 2 places down.");
-
+
output = Y.Number.format(-123.123, {decimalPlaces:2});
ASSERT.areSame("-123.12", output, "Incorrect decimal rounding to 2 places down neg.");
output = Y.Number.format(123.123, {decimalPlaces:1});
ASSERT.areSame("123.1", output, "Incorrect decimal rounding to 1 place.");
-
+
output = Y.Number.format(-123.123, {decimalPlaces:1});
ASSERT.areSame("-123.1", output, "Incorrect decimal rounding to 1 place neg.");
output = Y.Number.format(123.123, {decimalPlaces:0});
ASSERT.areSame("123", output, "Incorrect decimal rounding to 0 places.");
-
+
output = Y.Number.format(-123.123, {decimalPlaces:0});
ASSERT.areSame("-123", output, "Incorrect decimal rounding to 0 places neg.");
output = Y.Number.format(123.123, {decimalPlaces:-1});
ASSERT.areSame("123.123", output, "Must ignore decimalPlaces < 0.");
-
+
output = Y.Number.format(-123.123, {decimalPlaces:21});
ASSERT.areSame("-123.123", output, "Must ignore decimalPlaces > 20.");
},
-
+
testThousandsSeparator: function() {
var output = Y.Number.format(123123123, {thousandsSeparator:","});
ASSERT.areSame("123,123,123", output, "Incorrect thousands separation.");
-
+
output = Y.Number.format(-123123123, {thousandsSeparator:","});
ASSERT.areSame("-123,123,123", output, "Incorrect thousands separation neg.");
},
@@ -161,7 +216,7 @@ YUI.add('number-tests', function(Y) {
decimalSeparator:","
});
ASSERT.areSame("¥123.123.123,18", output, "Incorrect Yen formatting neg.");
-
+
output = Y.Number.format(-123123123.176,{
prefix: "¥",
decimalPlaces:2,
@@ -171,10 +226,11 @@ YUI.add('number-tests', function(Y) {
ASSERT.areSame("¥-123.123.123,18", output, "Incorrect Yen formatting neg.");
}
});
-
-
+
+
var suite = new Y.Test.Suite("Number");
suite.add(testParse);
+ suite.add(testParseWithConfig);
suite.add(testFormat);
Y.Test.Runner.add(suite);
From f52d1025538724fd02883a3c577f527a02203426 Mon Sep 17 00:00:00 2001
From: Daniel
Date: Fri, 5 Apr 2013 17:20:03 +0200
Subject: [PATCH 02/19] Moved the regular expression out of the method.
---
src/number/js/number-parse.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/number/js/number-parse.js b/src/number/js/number-parse.js
index d65913aff05..daf12a26cb9 100755
--- a/src/number/js/number-parse.js
+++ b/src/number/js/number-parse.js
@@ -6,7 +6,8 @@
* @for Number
*/
-var LANG = Y.Lang;
+var LANG = Y.Lang,
+ safeRegExp = /(\\|\[|\]|\.|\+|\*|\?|\^|\$|\(|\)|\||\{|\})/g;
Y.mix(Y.namespace("Number"), {
/**
@@ -29,7 +30,6 @@ Y.mix(Y.namespace("Number"), {
*/
parse: function(data, config) {
var number, r,
- safeRegExp = /(\\|\[|\]|\.|\+|\*|\?|\^|\$|\(|\)|\||\{|\})/g,
safe = function(r) {
return r.replace(safeRegExp,'\\$1');
};
From 8fd4ddc0fa6c153d5cabf1133927e603cb3feb90 Mon Sep 17 00:00:00 2001
From: Daniel
Date: Fri, 19 Apr 2013 10:21:16 +0200
Subject: [PATCH 03/19] Added a parse function builder. Said function gets
cached. Dropped uses of Y.Lang.
---
src/number/js/number-parse.js | 96 +++++++++++++-------
src/number/meta/number.json | 1 +
src/number/tests/unit/assets/number-tests.js | 3 +
3 files changed, 68 insertions(+), 32 deletions(-)
diff --git a/src/number/js/number-parse.js b/src/number/js/number-parse.js
index daf12a26cb9..e36f1d550ce 100755
--- a/src/number/js/number-parse.js
+++ b/src/number/js/number-parse.js
@@ -6,16 +6,56 @@
* @for Number
*/
-var LANG = Y.Lang,
- safeRegExp = /(\\|\[|\]|\.|\+|\*|\?|\^|\$|\(|\)|\||\{|\})/g;
+var safe = Y.Escape.regex,
+ SPACES = '\\s*';
Y.mix(Y.namespace("Number"), {
+ /**
+ * Returns a parsing function for the given configuration.
+ * It uses `Y.cached` so it expects the format spec separated into
+ * individual values.
+ * The method further uses closure to put together and save the
+ * regular expresssion just once in the outer function.
+ *
+ * @method _buildParser
+ * @param [prefix] {String} Prefix string to be stripped out.
+ * @param [suffix] {String} Suffix string to be stripped out.
+ * @param [separator] {String} Thousands separator to be stripped out.
+ * @param [decimal] {String} Decimal separator to be replaced by a dot.
+ * @return {Function} Parsing function.
+ * @private
+ */
+ _buildParser: Y.cached(function (prefix, suffix, separator, decimal) {
+ var regexBits = [],
+ regex;
+
+ if (prefix) {
+ regexBits.push('^' + SPACES + safe(prefix) + SPACES);
+ }
+ if (suffix) {
+ regexBits.push(SPACES + safe(suffix) + SPACES + '$');
+ }
+ if (separator) {
+ regexBits.push(safe(separator) + '(?=\\d)');
+ }
+
+ regex = new RegExp('(?:' + regexBits.join('|') + ')', 'g');
+
+ if (decimal === '.') {
+ decimal = null;
+ }
+ return function (val) {
+ val = val.replace(regex, '');
+
+ return decimal ? val.replace(decimal, '.') : val;
+ }
+ }),
/**
* Converts data to type Number.
* If a `config` argument is used, it will strip the `data` of the prefix,
* the suffix and the thousands separator, if any of them are found,
* replace the decimal separator by a dot and parse the resulting string.
- * Extra whitespace will be ignored.
+ * Extra whitespace around the prefix and suffix will be ignored.
*
* @method parse
* @param data {String | Number | Boolean} Data to convert. The following
@@ -28,39 +68,31 @@ Y.mix(Y.namespace("Number"), {
* @param [config.suffix] {HTML} String to be removed from the end of the number, like " items".
* @return {Number} A number, or null.
*/
+
parse: function(data, config) {
- var number, r,
- safe = function(r) {
- return r.replace(safeRegExp,'\\$1');
- };
- if (LANG.isString(data) && LANG.isObject(config)) {
- data = data.replace(/\s+/g, '');
- r = config.prefix;
+ var parser;
- if (r) {
- data = data.replace(new RegExp('^(\\s*' + safe(r) + ')') , '');
- }
- r = config.suffix;
- if (r) {
- data = data.replace(new RegExp('(' + safe(r) + '\\s*)$'),'');
- }
- r = config.thousandsSeparator;
- if (r) {
- data = data.replace(new RegExp(safe(r),'g'),'');
- }
- r = config.decimalSeparator;
- if (r && r !== '.') {
- data = data.replace(new RegExp(safe(r)),'.');
- }
- }
- number = (data === null || data === "") ? data : +data;
- if(LANG.isNumber(number)) {
- return number;
+ if (config && typeof data === 'string') {
+ parser = this._buildParser(config.prefix, config.suffix, config.thousandsSeparator, config.decimalSeparator);
+
+ data = parser(data);
}
- else {
- Y.log("Could not parse data to type Number", "warn", "number");
- return null;
+
+ if (data !== null && data !== "") {
+ data = +data;
+
+ // catch NaN and ±Infinity
+ if (!isFinite(data)) {
+ data = null;
+ }
+ } else {
+ data = null;
}
+
+ // on the same line to get stripped for raw/min.js by build system
+ if (data === null) { Y.log("Could not parse data to type Number", "warn", "number"); }
+
+ return data;
}
});
diff --git a/src/number/meta/number.json b/src/number/meta/number.json
index ee8ae3b6fc5..610f48a6dc9 100644
--- a/src/number/meta/number.json
+++ b/src/number/meta/number.json
@@ -6,6 +6,7 @@
]
},
"datatype-number-parse": {
+ "requires": ["escape"]
},
"datatype-number-format": {
}
diff --git a/src/number/tests/unit/assets/number-tests.js b/src/number/tests/unit/assets/number-tests.js
index b756ffb8a25..a2a4ef1dc24 100644
--- a/src/number/tests/unit/assets/number-tests.js
+++ b/src/number/tests/unit/assets/number-tests.js
@@ -49,6 +49,9 @@ YUI.add('number-tests', function(Y) {
tests: function () {
var i, v, values = [
["1234.5", {}, 1234.5],
+ ["1234.5", {
+ decimalSeparator: '.'
+ }, 1234.5],
["$1.234,50", {
prefix: '$',
decimalSeparator:',',
From fa08886d568244593aa9311c4af7cd6c44b64c6c Mon Sep 17 00:00:00 2001
From: Daniel
Date: Fri, 19 Apr 2013 17:45:44 +0200
Subject: [PATCH 04/19] I have to pay attention to JSHint messages. I have to
pay attention to JSHint messages. .... and so on.
---
src/number/js/number-parse.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/number/js/number-parse.js b/src/number/js/number-parse.js
index e36f1d550ce..c14a9cb5266 100755
--- a/src/number/js/number-parse.js
+++ b/src/number/js/number-parse.js
@@ -48,7 +48,7 @@ Y.mix(Y.namespace("Number"), {
val = val.replace(regex, '');
return decimal ? val.replace(decimal, '.') : val;
- }
+ };
}),
/**
* Converts data to type Number.
From 138ce7d880cf0f51f331cf85cdf144093244c388 Mon Sep 17 00:00:00 2001
From: Daniel
Date: Sat, 20 Apr 2013 18:04:45 +0200
Subject: [PATCH 05/19] Added some more tests for edge conditions. While trying
to write a better version I found some of the tests didn't cover some edge
conditions and they didn't check the groups of numbers were not reversed or
shuffled around.
---
src/number/tests/unit/assets/number-tests.js | 29 ++++++++++++++------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/src/number/tests/unit/assets/number-tests.js b/src/number/tests/unit/assets/number-tests.js
index a2a4ef1dc24..86fdacea068 100644
--- a/src/number/tests/unit/assets/number-tests.js
+++ b/src/number/tests/unit/assets/number-tests.js
@@ -204,29 +204,42 @@ YUI.add('number-tests', function(Y) {
},
testThousandsSeparator: function() {
- var output = Y.Number.format(123123123, {thousandsSeparator:","});
- ASSERT.areSame("123,123,123", output, "Incorrect thousands separation.");
+ var output = Y.Number.format(123456789, {thousandsSeparator:","});
+ ASSERT.areSame("123,456,789", output, "Incorrect thousands separation.");
+
+ output = Y.Number.format(-123456789, {thousandsSeparator:","});
+ ASSERT.areSame("-123,456,789", output, "Incorrect thousands separation neg.");
+
+ output = Y.Number.format(12345678, {thousandsSeparator:","});
+ ASSERT.areSame("12,345,678", output, "Incorrect thousands separation when (number-of-digits % 3) == 2");
+
+ output = Y.Number.format(1234567, {thousandsSeparator:","});
+ ASSERT.areSame("1,234,567", output, "Incorrect thousands separation when (number-of-digits % 3) == 1");
+
+ output = Y.Number.format(-12345678, {thousandsSeparator:","});
+ ASSERT.areSame("-12,345,678", output, "Incorrect thousands separation neg. when (number-of-digits % 3) == 2");
+
+ var output = Y.Number.format(12.34, {thousandsSeparator:","});
+ ASSERT.areSame("12.34", output, "Incorrect thousands separation when number is short.");
- output = Y.Number.format(-123123123, {thousandsSeparator:","});
- ASSERT.areSame("-123,123,123", output, "Incorrect thousands separation neg.");
},
testComplex: function() {
- var output = Y.Number.format(123123123.176,{
+ var output = Y.Number.format(123456789.176,{
prefix: "¥",
decimalPlaces:2,
thousandsSeparator:".",
decimalSeparator:","
});
- ASSERT.areSame("¥123.123.123,18", output, "Incorrect Yen formatting neg.");
+ ASSERT.areSame("¥123.456.789,18", output, "Incorrect Yen formatting neg.");
- output = Y.Number.format(-123123123.176,{
+ output = Y.Number.format(-123456789.176,{
prefix: "¥",
decimalPlaces:2,
thousandsSeparator:".",
decimalSeparator:","
});
- ASSERT.areSame("¥-123.123.123,18", output, "Incorrect Yen formatting neg.");
+ ASSERT.areSame("¥-123.456.789,18", output, "Incorrect Yen formatting neg.");
}
});
From 6db021a52411e60f92c45abdeecc532c5f44e782 Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Thu, 16 May 2013 12:48:16 -0700
Subject: [PATCH 06/19] Add Y.ArraySort.naturalCompare().
For performing natural-order comparisons of two strings, two numbers, or a
number and a string.
---
src/arraysort/HISTORY.md | 8 ++
src/arraysort/js/arraysort.js | 127 +++++++++++++++++-
.../tests/unit/assets/arraysort-tests.js | 62 +++++++++
3 files changed, 191 insertions(+), 6 deletions(-)
diff --git a/src/arraysort/HISTORY.md b/src/arraysort/HISTORY.md
index 59bd5e109a8..4c54426c09c 100644
--- a/src/arraysort/HISTORY.md
+++ b/src/arraysort/HISTORY.md
@@ -1,6 +1,14 @@
ArraySort Change History
========================
+@VERSION@
+------
+
+* Added `Y.ArraySort.naturalCompare`, which compares two strings or numbers (or
+ a number and a string) in natural order. This ensures that a value like 'foo1'
+ is sorted before 'foo10', whereas a standard ASCII sort would sort 'foo10'
+ first. [Ryan Grove]
+
3.10.1
------
diff --git a/src/arraysort/js/arraysort.js b/src/arraysort/js/arraysort.js
index 1f79cda980b..436593e5640 100644
--- a/src/arraysort/js/arraysort.js
+++ b/src/arraysort/js/arraysort.js
@@ -1,20 +1,24 @@
+/*jshint expr:true, onevar:false */
+
/**
-Provides a case-insenstive comparator which can be used for array sorting.
+Provides a comparator functions useful for sorting arrays.
@module arraysort
-*/
+**/
var LANG = Y.Lang,
ISVALUE = LANG.isValue,
ISSTRING = LANG.isString;
/**
-Provides a case-insenstive comparator which can be used for array sorting.
+Provides comparator functions useful for sorting arrays.
@class ArraySort
-*/
+@static
+**/
-Y.ArraySort = {
+var ArraySort = Y.ArraySort = {
+ // -- Public Methods -------------------------------------------------------
/**
Comparator function for simple case-insensitive sorting of an array of
@@ -26,6 +30,7 @@ Y.ArraySort = {
@param desc {Boolean} `true` if sort direction is descending, `false` if
sort direction is ascending.
@return {Boolean} -1 when a < b. 0 when a == b. 1 when a > b.
+ @static
*/
compare: function(a, b, desc) {
if(!ISVALUE(a)) {
@@ -55,6 +60,116 @@ Y.ArraySort = {
else {
return 0;
}
- }
+ },
+
+ /**
+ Performs a natural-order comparison of two strings or numbers (or a string
+ and a number). This ensures that a value like 'foo1' will be sorted before
+ 'foo10', whereas a standard ASCII sort would sort 'foo10' first.
+
+ @example
+
+ var items = ['item10', 'item2', 'item1', 10, '1', 2];
+
+ items.sort(Y.ArraySort.naturalCompare);
+ console.log(items); // => ['1', 2, 10, 'item1', 'item2', 'item10']
+
+ @method naturalCompare
+ @param {Number|String} a First value to compare.
+ @param {Number|String} b Second value to compare.
+ @param {Object} [options] Options.
+ @param [options.caseSensitive=false] If `true`, a case-sensitive
+ comparison will be performed. By default the comparison is
+ case-insensitive.
+ @param [options.descending=false] If `true`, the sort order will be
+ reversed so that larger values are sorted before smaller values.
+ @return {Number} `0` if the two items are equal, a negative number if _a_
+ should be sorted before _b_, or a positive number if _b_ should be
+ sorted before _a_.
+ @static
+ @since @SINCE@
+ **/
+ naturalCompare: function (a, b, options) {
+ // Coerce `a` and `b` to strings.
+ a += '';
+ b += '';
+
+ // Convert `a` and `b` to lowercase unless `options.caseSensitive` is
+ // truthy.
+ if (!options || !options.caseSensitive) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ }
+
+ // Split `a` and `b` into alpha parts and numeric parts.
+ var aParts = ArraySort._splitAlphaNum(a),
+ bParts = ArraySort._splitAlphaNum(b),
+ length = Math.min(aParts.length, bParts.length),
+ result = 0,
+
+ aPart,
+ bPart,
+ i;
+
+ // Compare each part of `a` with each part of `b`.
+ for (i = 0; i < length; i++) {
+ aPart = aParts[i];
+ bPart = bParts[i];
+
+ // If the two parts aren't equal, compare them and stop iterating.
+ if (aPart !== bPart) {
+ // First, try comparing them as numbers.
+ result = aPart - bPart;
+ // If that didn't work, compare them as strings. This falsiness
+ // check works because `result` can't be 0 (we checked for
+ // equality above) and NaN is falsy.
+ if (!result) {
+ result = aPart > bPart ? 1 : -1;
+ }
+
+ // At this point we know enough to be able to sort the two
+ // strings, so we don't need to compare any more parts.
+ break;
+ }
+ }
+
+ // If we get here and `result` is still 0, then sort the shorter string
+ // before the longer string.
+ result || (result = a.length - b.length);
+
+ // Return the result, flipping the order if `options.descending` is
+ // truthy.
+ return options && options.descending ? -result : result;
+ },
+
+ // -- Protected Methods ----------------------------------------------------
+
+ /**
+ Splits a string into an array of alpha character and digit character parts.
+
+ @example
+
+ Y.ArraySort._splitAlphaNum('abc123def456');
+ // => ['abc', '123', 'def', '456']
+
+ @method _splitAlphaNum
+ @param {String} string String to split.
+ @return {String[]} Array of alpha parts and digit parts.
+ @protected
+ @static
+ @since @SINCE@
+ **/
+ _splitAlphaNum: function (string) {
+ /*jshint boss:true */
+ var parts = [],
+ regex = /(\d+|\D+)/g,
+ match;
+
+ while (match = regex.exec(string)) { // assignment
+ parts.push(match[1]);
+ }
+
+ return parts;
+ }
};
diff --git a/src/arraysort/tests/unit/assets/arraysort-tests.js b/src/arraysort/tests/unit/assets/arraysort-tests.js
index 0ed4144d996..1398e6d9a7a 100644
--- a/src/arraysort/tests/unit/assets/arraysort-tests.js
+++ b/src/arraysort/tests/unit/assets/arraysort-tests.js
@@ -26,5 +26,67 @@ YUI.add('arraysort-tests', function(Y) {
}
}));
+ suite.add(new Y.Test.Case({
+ name: 'naturalCompare()',
+
+ 'should sort strings in natural order': function () {
+ var items = [
+ 'foo', 'foo10', 'foo2', 'foo1', 'foo200', 'bar', '2', '1', '10',
+ '1foo', '10foo', 'foo2', 'bar', '1'
+ ];
+
+ items.sort(Y.ArraySort.naturalCompare);
+
+ ArrayAssert.itemsAreSame([
+ '1', '1', '1foo', '2', '10', '10foo', 'bar', 'bar', 'foo',
+ 'foo1', 'foo2', 'foo2', 'foo10', 'foo200'
+ ], items);
+ },
+
+ 'should sort mixed strings and numbers': function () {
+ var items = [100, '100', '1', 0, '5', 10, 'a', '9a'];
+
+ items.sort(Y.ArraySort.naturalCompare);
+
+ ArrayAssert.itemsAreSame([
+ 0, '1', '5', '9a', 10, 100, '100', 'a'
+ ], items);
+ },
+
+ 'should be case-insensitive by default': function () {
+ var items = ['Foo', 'bar', 'Baz', 'quux'];
+
+ items.sort(Y.ArraySort.naturalCompare);
+
+ ArrayAssert.itemsAreSame(['bar', 'Baz', 'Foo', 'quux'], items);
+ },
+
+ 'should be case-sensitive when `options.caseSensitive` is truthy': function () {
+ var items = ['Foo', 'bar', 'Baz', 'quux'];
+
+ items.sort(function (a, b) {
+ return Y.ArraySort.naturalCompare(a, b, {caseSensitive: true});
+ });
+
+ ArrayAssert.itemsAreSame(['Baz', 'Foo', 'bar', 'quux'], items);
+ },
+
+ 'should sort in descending order when `options.descending` is truthy': function () {
+ var items = [
+ 'foo', 'foo10', 'foo2', 'foo1', 'foo200', 'bar', '2', '1', '10',
+ '1foo', '10foo', 'foo2', 'bar', '1'
+ ];
+
+ items.sort(function (a, b) {
+ return Y.ArraySort.naturalCompare(a, b, {descending: true});
+ });
+
+ ArrayAssert.itemsAreSame([
+ 'foo200', 'foo10', 'foo2', 'foo2', 'foo1', 'foo', 'bar', 'bar',
+ '10foo', '10', '2', '1foo', '1', '1'
+ ], items);
+ }
+ }));
+
Y.Test.Runner.add(suite);
});
From 8ead3821f590c19a2955ce94bb299b24ea6cd2a7 Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Thu, 16 May 2013 13:14:19 -0700
Subject: [PATCH 07/19] Fix comment typo.
---
src/arraysort/js/arraysort.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/arraysort/js/arraysort.js b/src/arraysort/js/arraysort.js
index 436593e5640..7f79e0ce028 100644
--- a/src/arraysort/js/arraysort.js
+++ b/src/arraysort/js/arraysort.js
@@ -1,7 +1,7 @@
/*jshint expr:true, onevar:false */
/**
-Provides a comparator functions useful for sorting arrays.
+Provides comparator functions useful for sorting arrays.
@module arraysort
**/
From d84970aa8f39649967ce5fcb10fb4a670787042e Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Thu, 16 May 2013 13:30:10 -0700
Subject: [PATCH 08/19] Fix lint errors in ArraySort tests.
---
src/arraysort/tests/unit/assets/arraysort-tests.js | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/arraysort/tests/unit/assets/arraysort-tests.js b/src/arraysort/tests/unit/assets/arraysort-tests.js
index 1398e6d9a7a..3077b7fc1d3 100644
--- a/src/arraysort/tests/unit/assets/arraysort-tests.js
+++ b/src/arraysort/tests/unit/assets/arraysort-tests.js
@@ -1,6 +1,5 @@
YUI.add('arraysort-tests', function(Y) {
- var Assert = Y.Assert,
- ArrayAssert = Y.ArrayAssert,
+ var ArrayAssert = Y.ArrayAssert,
suite = new Y.Test.Suite('ArraySort');
@@ -9,19 +8,19 @@ YUI.add('arraysort-tests', function(Y) {
'should compare numbers': function () {
var array = [2,1,3,5,4];
- array.sort(Y.ArraySort.compare)
+ array.sort(Y.ArraySort.compare);
ArrayAssert.itemsAreSame([1,2,3,4,5], array, "Expected sorted numbers.");
},
'should compare strings': function () {
var array = ["caa", "baa", "bba", "aba", "cba", "aaa", "abc"];
- array.sort(Y.ArraySort.compare)
+ array.sort(Y.ArraySort.compare);
ArrayAssert.itemsAreSame(["aaa","aba","abc","baa","bba","caa","cba"], array, "Expected sorted strings.");
},
'should compare mixed alpha and numeric strings': function() {
var array = ["attic", "Aardvark", "1", "0", "Zoo", "zebra"];
- array.sort(Y.ArraySort.compare)
+ array.sort(Y.ArraySort.compare);
ArrayAssert.itemsAreSame(["0", "1", "Aardvark","attic","zebra","Zoo"], array, "Expected sorted mixed strings.");
}
}));
From 1a98a3d6b1e6b057d735417173c55ab84b79dbd8 Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Thu, 16 May 2013 13:43:55 -0700
Subject: [PATCH 09/19] Fix missing types in naturalCompare() API docs.
---
src/arraysort/js/arraysort.js | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/arraysort/js/arraysort.js b/src/arraysort/js/arraysort.js
index 7f79e0ce028..cd72eb5d31f 100644
--- a/src/arraysort/js/arraysort.js
+++ b/src/arraysort/js/arraysort.js
@@ -78,11 +78,12 @@ var ArraySort = Y.ArraySort = {
@param {Number|String} a First value to compare.
@param {Number|String} b Second value to compare.
@param {Object} [options] Options.
- @param [options.caseSensitive=false] If `true`, a case-sensitive
- comparison will be performed. By default the comparison is
- case-insensitive.
- @param [options.descending=false] If `true`, the sort order will be
- reversed so that larger values are sorted before smaller values.
+ @param {Boolean} [options.caseSensitive=false] If `true`, a
+ case-sensitive comparison will be performed. By default the
+ comparison is case-insensitive.
+ @param {Boolean} [options.descending=false] If `true`, the sort order
+ will be reversed so that larger values are sorted before smaller
+ values.
@return {Number} `0` if the two items are equal, a negative number if _a_
should be sorted before _b_, or a positive number if _b_ should be
sorted before _a_.
From b2c48274bd25bce9e12f7145d17cbfa352249695 Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Thu, 16 May 2013 14:12:01 -0700
Subject: [PATCH 10/19] 'foo10' would be sorted before 'foo2', not 'foo1'.
---
src/arraysort/HISTORY.md | 8 ++++----
src/arraysort/js/arraysort.js | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/arraysort/HISTORY.md b/src/arraysort/HISTORY.md
index 4c54426c09c..ee7396b9b3b 100644
--- a/src/arraysort/HISTORY.md
+++ b/src/arraysort/HISTORY.md
@@ -4,10 +4,10 @@ ArraySort Change History
@VERSION@
------
-* Added `Y.ArraySort.naturalCompare`, which compares two strings or numbers (or
- a number and a string) in natural order. This ensures that a value like 'foo1'
- is sorted before 'foo10', whereas a standard ASCII sort would sort 'foo10'
- first. [Ryan Grove]
+* Added `Y.ArraySort.naturalCompare()`, which compares two strings or numbers
+ (or a number and a string) in natural order. This ensures that a value like
+ 'foo2' is sorted before 'foo10', whereas a standard ASCII sort would sort
+ 'foo10' first. [Ryan Grove]
3.10.1
------
diff --git a/src/arraysort/js/arraysort.js b/src/arraysort/js/arraysort.js
index cd72eb5d31f..bf0f46a0fd6 100644
--- a/src/arraysort/js/arraysort.js
+++ b/src/arraysort/js/arraysort.js
@@ -64,7 +64,7 @@ var ArraySort = Y.ArraySort = {
/**
Performs a natural-order comparison of two strings or numbers (or a string
- and a number). This ensures that a value like 'foo1' will be sorted before
+ and a number). This ensures that a value like 'foo2' will be sorted before
'foo10', whereas a standard ASCII sort would sort 'foo10' first.
@example
From ae0d4327b564cda8782f5e80096af006335d6887 Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Mon, 20 May 2013 10:14:08 -0700
Subject: [PATCH 11/19] Build arraysort.
---
build/arraysort/arraysort-coverage.js | 4 +-
build/arraysort/arraysort-debug.js | 128 ++++++++++++++++++++++++--
build/arraysort/arraysort-min.js | 2 +-
build/arraysort/arraysort.js | 128 ++++++++++++++++++++++++--
4 files changed, 247 insertions(+), 15 deletions(-)
diff --git a/build/arraysort/arraysort-coverage.js b/build/arraysort/arraysort-coverage.js
index 6c345f413a5..19791dbee75 100644
--- a/build/arraysort/arraysort-coverage.js
+++ b/build/arraysort/arraysort-coverage.js
@@ -1,6 +1,6 @@
if (typeof __coverage__ === 'undefined') { __coverage__ = {}; }
if (!__coverage__['build/arraysort/arraysort.js']) {
- __coverage__['build/arraysort/arraysort.js'] = {"path":"build/arraysort/arraysort.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0]},"f":{"1":0,"2":0},"fnMap":{"1":{"name":"(anonymous_1)","line":1,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":40}}},"2":{"name":"(anonymous_2)","line":32,"loc":{"start":{"line":32,"column":13},"end":{"line":32,"column":34}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":65,"column":44}},"2":{"start":{"line":9,"column":0},"end":{"line":11,"column":29}},"3":{"start":{"line":19,"column":0},"end":{"line":62,"column":2}},"4":{"start":{"line":33,"column":8},"end":{"line":43,"column":9}},"5":{"start":{"line":34,"column":12},"end":{"line":39,"column":13}},"6":{"start":{"line":35,"column":16},"end":{"line":35,"column":25}},"7":{"start":{"line":38,"column":16},"end":{"line":38,"column":25}},"8":{"start":{"line":41,"column":13},"end":{"line":43,"column":9}},"9":{"start":{"line":42,"column":12},"end":{"line":42,"column":22}},"10":{"start":{"line":45,"column":8},"end":{"line":47,"column":9}},"11":{"start":{"line":46,"column":12},"end":{"line":46,"column":32}},"12":{"start":{"line":48,"column":8},"end":{"line":50,"column":9}},"13":{"start":{"line":49,"column":12},"end":{"line":49,"column":32}},"14":{"start":{"line":51,"column":8},"end":{"line":59,"column":9}},"15":{"start":{"line":52,"column":12},"end":{"line":52,"column":35}},"16":{"start":{"line":54,"column":13},"end":{"line":59,"column":9}},"17":{"start":{"line":55,"column":12},"end":{"line":55,"column":35}},"18":{"start":{"line":58,"column":12},"end":{"line":58,"column":21}}},"branchMap":{"1":{"line":33,"type":"if","locations":[{"start":{"line":33,"column":8},"end":{"line":33,"column":8}},{"start":{"line":33,"column":8},"end":{"line":33,"column":8}}]},"2":{"line":34,"type":"if","locations":[{"start":{"line":34,"column":12},"end":{"line":34,"column":12}},{"start":{"line":34,"column":12},"end":{"line":34,"column":12}}]},"3":{"line":41,"type":"if","locations":[{"start":{"line":41,"column":13},"end":{"line":41,"column":13}},{"start":{"line":41,"column":13},"end":{"line":41,"column":13}}]},"4":{"line":45,"type":"if","locations":[{"start":{"line":45,"column":8},"end":{"line":45,"column":8}},{"start":{"line":45,"column":8},"end":{"line":45,"column":8}}]},"5":{"line":48,"type":"if","locations":[{"start":{"line":48,"column":8},"end":{"line":48,"column":8}},{"start":{"line":48,"column":8},"end":{"line":48,"column":8}}]},"6":{"line":51,"type":"if","locations":[{"start":{"line":51,"column":8},"end":{"line":51,"column":8}},{"start":{"line":51,"column":8},"end":{"line":51,"column":8}}]},"7":{"line":52,"type":"cond-expr","locations":[{"start":{"line":52,"column":28},"end":{"line":52,"column":29}},{"start":{"line":52,"column":32},"end":{"line":52,"column":34}}]},"8":{"line":54,"type":"if","locations":[{"start":{"line":54,"column":13},"end":{"line":54,"column":13}},{"start":{"line":54,"column":13},"end":{"line":54,"column":13}}]},"9":{"line":55,"type":"cond-expr","locations":[{"start":{"line":55,"column":28},"end":{"line":55,"column":30}},{"start":{"line":55,"column":33},"end":{"line":55,"column":34}}]}},"code":["(function () { YUI.add('arraysort', function (Y, NAME) {","","/**","Provides a case-insenstive comparator which can be used for array sorting.","","@module arraysort","*/","","var LANG = Y.Lang,"," ISVALUE = LANG.isValue,"," ISSTRING = LANG.isString;","","/**","Provides a case-insenstive comparator which can be used for array sorting.","","@class ArraySort","*/","","Y.ArraySort = {",""," /**"," Comparator function for simple case-insensitive sorting of an array of"," strings.",""," @method compare"," @param a {Object} First sort argument."," @param b {Object} Second sort argument."," @param desc {Boolean} `true` if sort direction is descending, `false` if"," sort direction is ascending."," @return {Boolean} -1 when a < b. 0 when a == b. 1 when a > b."," */"," compare: function(a, b, desc) {"," if(!ISVALUE(a)) {"," if(!ISVALUE(b)) {"," return 0;"," }"," else {"," return 1;"," }"," }"," else if(!ISVALUE(b)) {"," return -1;"," }",""," if(ISSTRING(a)) {"," a = a.toLowerCase();"," }"," if(ISSTRING(b)) {"," b = b.toLowerCase();"," }"," if(a < b) {"," return (desc) ? 1 : -1;"," }"," else if (a > b) {"," return (desc) ? -1 : 1;"," }"," else {"," return 0;"," }"," }","","};","","","}, '@VERSION@', {\"requires\": [\"yui-base\"]});","","}());"]};
+ __coverage__['build/arraysort/arraysort.js'] = {"path":"build/arraysort/arraysort.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0]},"f":{"1":0,"2":0,"3":0,"4":0},"fnMap":{"1":{"name":"(anonymous_1)","line":1,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":40}}},"2":{"name":"(anonymous_2)","line":37,"loc":{"start":{"line":37,"column":13},"end":{"line":37,"column":34}}},"3":{"name":"(anonymous_3)","line":95,"loc":{"start":{"line":95,"column":20},"end":{"line":95,"column":45}}},"4":{"name":"(anonymous_4)","line":166,"loc":{"start":{"line":166,"column":20},"end":{"line":166,"column":38}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":181,"column":44}},"2":{"start":{"line":11,"column":0},"end":{"line":13,"column":29}},"3":{"start":{"line":22,"column":0},"end":{"line":178,"column":2}},"4":{"start":{"line":38,"column":8},"end":{"line":48,"column":9}},"5":{"start":{"line":39,"column":12},"end":{"line":44,"column":13}},"6":{"start":{"line":40,"column":16},"end":{"line":40,"column":25}},"7":{"start":{"line":43,"column":16},"end":{"line":43,"column":25}},"8":{"start":{"line":46,"column":13},"end":{"line":48,"column":9}},"9":{"start":{"line":47,"column":12},"end":{"line":47,"column":22}},"10":{"start":{"line":50,"column":8},"end":{"line":52,"column":9}},"11":{"start":{"line":51,"column":12},"end":{"line":51,"column":32}},"12":{"start":{"line":53,"column":8},"end":{"line":55,"column":9}},"13":{"start":{"line":54,"column":12},"end":{"line":54,"column":32}},"14":{"start":{"line":56,"column":8},"end":{"line":64,"column":9}},"15":{"start":{"line":57,"column":12},"end":{"line":57,"column":35}},"16":{"start":{"line":59,"column":13},"end":{"line":64,"column":9}},"17":{"start":{"line":60,"column":12},"end":{"line":60,"column":35}},"18":{"start":{"line":63,"column":12},"end":{"line":63,"column":21}},"19":{"start":{"line":97,"column":8},"end":{"line":97,"column":16}},"20":{"start":{"line":98,"column":8},"end":{"line":98,"column":16}},"21":{"start":{"line":102,"column":8},"end":{"line":105,"column":9}},"22":{"start":{"line":103,"column":12},"end":{"line":103,"column":32}},"23":{"start":{"line":104,"column":12},"end":{"line":104,"column":32}},"24":{"start":{"line":108,"column":8},"end":{"line":115,"column":14}},"25":{"start":{"line":118,"column":8},"end":{"line":138,"column":9}},"26":{"start":{"line":119,"column":12},"end":{"line":119,"column":30}},"27":{"start":{"line":120,"column":12},"end":{"line":120,"column":30}},"28":{"start":{"line":123,"column":12},"end":{"line":137,"column":13}},"29":{"start":{"line":125,"column":16},"end":{"line":125,"column":39}},"30":{"start":{"line":130,"column":16},"end":{"line":132,"column":17}},"31":{"start":{"line":131,"column":20},"end":{"line":131,"column":52}},"32":{"start":{"line":136,"column":16},"end":{"line":136,"column":22}},"33":{"start":{"line":142,"column":8},"end":{"line":142,"column":49}},"34":{"start":{"line":146,"column":8},"end":{"line":146,"column":64}},"35":{"start":{"line":168,"column":8},"end":{"line":170,"column":18}},"36":{"start":{"line":172,"column":8},"end":{"line":174,"column":9}},"37":{"start":{"line":173,"column":12},"end":{"line":173,"column":33}},"38":{"start":{"line":176,"column":8},"end":{"line":176,"column":21}}},"branchMap":{"1":{"line":38,"type":"if","locations":[{"start":{"line":38,"column":8},"end":{"line":38,"column":8}},{"start":{"line":38,"column":8},"end":{"line":38,"column":8}}]},"2":{"line":39,"type":"if","locations":[{"start":{"line":39,"column":12},"end":{"line":39,"column":12}},{"start":{"line":39,"column":12},"end":{"line":39,"column":12}}]},"3":{"line":46,"type":"if","locations":[{"start":{"line":46,"column":13},"end":{"line":46,"column":13}},{"start":{"line":46,"column":13},"end":{"line":46,"column":13}}]},"4":{"line":50,"type":"if","locations":[{"start":{"line":50,"column":8},"end":{"line":50,"column":8}},{"start":{"line":50,"column":8},"end":{"line":50,"column":8}}]},"5":{"line":53,"type":"if","locations":[{"start":{"line":53,"column":8},"end":{"line":53,"column":8}},{"start":{"line":53,"column":8},"end":{"line":53,"column":8}}]},"6":{"line":56,"type":"if","locations":[{"start":{"line":56,"column":8},"end":{"line":56,"column":8}},{"start":{"line":56,"column":8},"end":{"line":56,"column":8}}]},"7":{"line":57,"type":"cond-expr","locations":[{"start":{"line":57,"column":28},"end":{"line":57,"column":29}},{"start":{"line":57,"column":32},"end":{"line":57,"column":34}}]},"8":{"line":59,"type":"if","locations":[{"start":{"line":59,"column":13},"end":{"line":59,"column":13}},{"start":{"line":59,"column":13},"end":{"line":59,"column":13}}]},"9":{"line":60,"type":"cond-expr","locations":[{"start":{"line":60,"column":28},"end":{"line":60,"column":30}},{"start":{"line":60,"column":33},"end":{"line":60,"column":34}}]},"10":{"line":102,"type":"if","locations":[{"start":{"line":102,"column":8},"end":{"line":102,"column":8}},{"start":{"line":102,"column":8},"end":{"line":102,"column":8}}]},"11":{"line":102,"type":"binary-expr","locations":[{"start":{"line":102,"column":12},"end":{"line":102,"column":20}},{"start":{"line":102,"column":24},"end":{"line":102,"column":46}}]},"12":{"line":123,"type":"if","locations":[{"start":{"line":123,"column":12},"end":{"line":123,"column":12}},{"start":{"line":123,"column":12},"end":{"line":123,"column":12}}]},"13":{"line":130,"type":"if","locations":[{"start":{"line":130,"column":16},"end":{"line":130,"column":16}},{"start":{"line":130,"column":16},"end":{"line":130,"column":16}}]},"14":{"line":131,"type":"cond-expr","locations":[{"start":{"line":131,"column":45},"end":{"line":131,"column":46}},{"start":{"line":131,"column":49},"end":{"line":131,"column":51}}]},"15":{"line":142,"type":"binary-expr","locations":[{"start":{"line":142,"column":8},"end":{"line":142,"column":14}},{"start":{"line":142,"column":19},"end":{"line":142,"column":47}}]},"16":{"line":146,"type":"cond-expr","locations":[{"start":{"line":146,"column":47},"end":{"line":146,"column":54}},{"start":{"line":146,"column":57},"end":{"line":146,"column":63}}]},"17":{"line":146,"type":"binary-expr","locations":[{"start":{"line":146,"column":15},"end":{"line":146,"column":22}},{"start":{"line":146,"column":26},"end":{"line":146,"column":44}}]}},"code":["(function () { YUI.add('arraysort', function (Y, NAME) {","","/*jshint expr:true, onevar:false */","","/**","Provides comparator functions useful for sorting arrays.","","@module arraysort","**/","","var LANG = Y.Lang,"," ISVALUE = LANG.isValue,"," ISSTRING = LANG.isString;","","/**","Provides comparator functions useful for sorting arrays.","","@class ArraySort","@static","**/","","var ArraySort = Y.ArraySort = {"," // -- Public Methods -------------------------------------------------------",""," /**"," Comparator function for simple case-insensitive sorting of an array of"," strings.",""," @method compare"," @param a {Object} First sort argument."," @param b {Object} Second sort argument."," @param desc {Boolean} `true` if sort direction is descending, `false` if"," sort direction is ascending."," @return {Boolean} -1 when a < b. 0 when a == b. 1 when a > b."," @static"," */"," compare: function(a, b, desc) {"," if(!ISVALUE(a)) {"," if(!ISVALUE(b)) {"," return 0;"," }"," else {"," return 1;"," }"," }"," else if(!ISVALUE(b)) {"," return -1;"," }",""," if(ISSTRING(a)) {"," a = a.toLowerCase();"," }"," if(ISSTRING(b)) {"," b = b.toLowerCase();"," }"," if(a < b) {"," return (desc) ? 1 : -1;"," }"," else if (a > b) {"," return (desc) ? -1 : 1;"," }"," else {"," return 0;"," }"," },",""," /**"," Performs a natural-order comparison of two strings or numbers (or a string"," and a number). This ensures that a value like 'foo2' will be sorted before"," 'foo10', whereas a standard ASCII sort would sort 'foo10' first.",""," @example",""," var items = ['item10', 'item2', 'item1', 10, '1', 2];",""," items.sort(Y.ArraySort.naturalCompare);"," console.log(items); // => ['1', 2, 10, 'item1', 'item2', 'item10']",""," @method naturalCompare"," @param {Number|String} a First value to compare."," @param {Number|String} b Second value to compare."," @param {Object} [options] Options."," @param {Boolean} [options.caseSensitive=false] If `true`, a"," case-sensitive comparison will be performed. By default the"," comparison is case-insensitive."," @param {Boolean} [options.descending=false] If `true`, the sort order"," will be reversed so that larger values are sorted before smaller"," values."," @return {Number} `0` if the two items are equal, a negative number if _a_"," should be sorted before _b_, or a positive number if _b_ should be"," sorted before _a_."," @static"," @since @SINCE@"," **/"," naturalCompare: function (a, b, options) {"," // Coerce `a` and `b` to strings."," a += '';"," b += '';",""," // Convert `a` and `b` to lowercase unless `options.caseSensitive` is"," // truthy."," if (!options || !options.caseSensitive) {"," a = a.toLowerCase();"," b = b.toLowerCase();"," }",""," // Split `a` and `b` into alpha parts and numeric parts."," var aParts = ArraySort._splitAlphaNum(a),"," bParts = ArraySort._splitAlphaNum(b),"," length = Math.min(aParts.length, bParts.length),"," result = 0,",""," aPart,"," bPart,"," i;",""," // Compare each part of `a` with each part of `b`."," for (i = 0; i < length; i++) {"," aPart = aParts[i];"," bPart = bParts[i];",""," // If the two parts aren't equal, compare them and stop iterating."," if (aPart !== bPart) {"," // First, try comparing them as numbers."," result = aPart - bPart;",""," // If that didn't work, compare them as strings. This falsiness"," // check works because `result` can't be 0 (we checked for"," // equality above) and NaN is falsy."," if (!result) {"," result = aPart > bPart ? 1 : -1;"," }",""," // At this point we know enough to be able to sort the two"," // strings, so we don't need to compare any more parts."," break;"," }"," }",""," // If we get here and `result` is still 0, then sort the shorter string"," // before the longer string."," result || (result = a.length - b.length);",""," // Return the result, flipping the order if `options.descending` is"," // truthy."," return options && options.descending ? -result : result;"," },",""," // -- Protected Methods ----------------------------------------------------",""," /**"," Splits a string into an array of alpha character and digit character parts.",""," @example",""," Y.ArraySort._splitAlphaNum('abc123def456');"," // => ['abc', '123', 'def', '456']",""," @method _splitAlphaNum"," @param {String} string String to split."," @return {String[]} Array of alpha parts and digit parts."," @protected"," @static"," @since @SINCE@"," **/"," _splitAlphaNum: function (string) {"," /*jshint boss:true */"," var parts = [],"," regex = /(\\d+|\\D+)/g,"," match;",""," while (match = regex.exec(string)) { // assignment"," parts.push(match[1]);"," }",""," return parts;"," }","};","","","}, '@VERSION@', {\"requires\": [\"yui-base\"]});","","}());"]};
}
var __cov_2Khzwsx6508tuwKeeJiIQQ = __coverage__['build/arraysort/arraysort.js'];
-__cov_2Khzwsx6508tuwKeeJiIQQ.s['1']++;YUI.add('arraysort',function(Y,NAME){__cov_2Khzwsx6508tuwKeeJiIQQ.f['1']++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['2']++;var LANG=Y.Lang,ISVALUE=LANG.isValue,ISSTRING=LANG.isString;__cov_2Khzwsx6508tuwKeeJiIQQ.s['3']++;Y.ArraySort={compare:function(a,b,desc){__cov_2Khzwsx6508tuwKeeJiIQQ.f['2']++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['4']++;if(!ISVALUE(a)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['1'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['5']++;if(!ISVALUE(b)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['2'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['6']++;return 0;}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['2'][1]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['7']++;return 1;}}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['1'][1]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['8']++;if(!ISVALUE(b)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['3'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['9']++;return-1;}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['3'][1]++;}}__cov_2Khzwsx6508tuwKeeJiIQQ.s['10']++;if(ISSTRING(a)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['4'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['11']++;a=a.toLowerCase();}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['4'][1]++;}__cov_2Khzwsx6508tuwKeeJiIQQ.s['12']++;if(ISSTRING(b)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['5'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['13']++;b=b.toLowerCase();}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['5'][1]++;}__cov_2Khzwsx6508tuwKeeJiIQQ.s['14']++;if(ab){__cov_2Khzwsx6508tuwKeeJiIQQ.b['8'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['17']++;return desc?(__cov_2Khzwsx6508tuwKeeJiIQQ.b['9'][0]++,-1):(__cov_2Khzwsx6508tuwKeeJiIQQ.b['9'][1]++,1);}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['8'][1]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['18']++;return 0;}}}};},'@VERSION@',{'requires':['yui-base']});
+__cov_2Khzwsx6508tuwKeeJiIQQ.s['1']++;YUI.add('arraysort',function(Y,NAME){__cov_2Khzwsx6508tuwKeeJiIQQ.f['1']++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['2']++;var LANG=Y.Lang,ISVALUE=LANG.isValue,ISSTRING=LANG.isString;__cov_2Khzwsx6508tuwKeeJiIQQ.s['3']++;var ArraySort=Y.ArraySort={compare:function(a,b,desc){__cov_2Khzwsx6508tuwKeeJiIQQ.f['2']++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['4']++;if(!ISVALUE(a)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['1'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['5']++;if(!ISVALUE(b)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['2'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['6']++;return 0;}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['2'][1]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['7']++;return 1;}}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['1'][1]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['8']++;if(!ISVALUE(b)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['3'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['9']++;return-1;}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['3'][1]++;}}__cov_2Khzwsx6508tuwKeeJiIQQ.s['10']++;if(ISSTRING(a)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['4'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['11']++;a=a.toLowerCase();}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['4'][1]++;}__cov_2Khzwsx6508tuwKeeJiIQQ.s['12']++;if(ISSTRING(b)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['5'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['13']++;b=b.toLowerCase();}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['5'][1]++;}__cov_2Khzwsx6508tuwKeeJiIQQ.s['14']++;if(ab){__cov_2Khzwsx6508tuwKeeJiIQQ.b['8'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['17']++;return desc?(__cov_2Khzwsx6508tuwKeeJiIQQ.b['9'][0]++,-1):(__cov_2Khzwsx6508tuwKeeJiIQQ.b['9'][1]++,1);}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['8'][1]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['18']++;return 0;}}},naturalCompare:function(a,b,options){__cov_2Khzwsx6508tuwKeeJiIQQ.f['3']++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['19']++;a+='';__cov_2Khzwsx6508tuwKeeJiIQQ.s['20']++;b+='';__cov_2Khzwsx6508tuwKeeJiIQQ.s['21']++;if((__cov_2Khzwsx6508tuwKeeJiIQQ.b['11'][0]++,!options)||(__cov_2Khzwsx6508tuwKeeJiIQQ.b['11'][1]++,!options.caseSensitive)){__cov_2Khzwsx6508tuwKeeJiIQQ.b['10'][0]++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['22']++;a=a.toLowerCase();__cov_2Khzwsx6508tuwKeeJiIQQ.s['23']++;b=b.toLowerCase();}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['10'][1]++;}__cov_2Khzwsx6508tuwKeeJiIQQ.s['24']++;var aParts=ArraySort._splitAlphaNum(a),bParts=ArraySort._splitAlphaNum(b),length=Math.min(aParts.length,bParts.length),result=0,aPart,bPart,i;__cov_2Khzwsx6508tuwKeeJiIQQ.s['25']++;for(i=0;ibPart?(__cov_2Khzwsx6508tuwKeeJiIQQ.b['14'][0]++,1):(__cov_2Khzwsx6508tuwKeeJiIQQ.b['14'][1]++,-1);}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['13'][1]++;}__cov_2Khzwsx6508tuwKeeJiIQQ.s['32']++;break;}else{__cov_2Khzwsx6508tuwKeeJiIQQ.b['12'][1]++;}}__cov_2Khzwsx6508tuwKeeJiIQQ.s['33']++;(__cov_2Khzwsx6508tuwKeeJiIQQ.b['15'][0]++,result)||(__cov_2Khzwsx6508tuwKeeJiIQQ.b['15'][1]++,result=a.length-b.length);__cov_2Khzwsx6508tuwKeeJiIQQ.s['34']++;return(__cov_2Khzwsx6508tuwKeeJiIQQ.b['17'][0]++,options)&&(__cov_2Khzwsx6508tuwKeeJiIQQ.b['17'][1]++,options.descending)?(__cov_2Khzwsx6508tuwKeeJiIQQ.b['16'][0]++,-result):(__cov_2Khzwsx6508tuwKeeJiIQQ.b['16'][1]++,result);},_splitAlphaNum:function(string){__cov_2Khzwsx6508tuwKeeJiIQQ.f['4']++;__cov_2Khzwsx6508tuwKeeJiIQQ.s['35']++;var parts=[],regex=/(\d+|\D+)/g,match;__cov_2Khzwsx6508tuwKeeJiIQQ.s['36']++;while(match=regex.exec(string)){__cov_2Khzwsx6508tuwKeeJiIQQ.s['37']++;parts.push(match[1]);}__cov_2Khzwsx6508tuwKeeJiIQQ.s['38']++;return parts;}};},'@VERSION@',{'requires':['yui-base']});
diff --git a/build/arraysort/arraysort-debug.js b/build/arraysort/arraysort-debug.js
index 9e6fa76a23a..745fde44de2 100644
--- a/build/arraysort/arraysort-debug.js
+++ b/build/arraysort/arraysort-debug.js
@@ -1,22 +1,26 @@
YUI.add('arraysort', function (Y, NAME) {
+/*jshint expr:true, onevar:false */
+
/**
-Provides a case-insenstive comparator which can be used for array sorting.
+Provides comparator functions useful for sorting arrays.
@module arraysort
-*/
+**/
var LANG = Y.Lang,
ISVALUE = LANG.isValue,
ISSTRING = LANG.isString;
/**
-Provides a case-insenstive comparator which can be used for array sorting.
+Provides comparator functions useful for sorting arrays.
@class ArraySort
-*/
+@static
+**/
-Y.ArraySort = {
+var ArraySort = Y.ArraySort = {
+ // -- Public Methods -------------------------------------------------------
/**
Comparator function for simple case-insensitive sorting of an array of
@@ -28,6 +32,7 @@ Y.ArraySort = {
@param desc {Boolean} `true` if sort direction is descending, `false` if
sort direction is ascending.
@return {Boolean} -1 when a < b. 0 when a == b. 1 when a > b.
+ @static
*/
compare: function(a, b, desc) {
if(!ISVALUE(a)) {
@@ -57,8 +62,119 @@ Y.ArraySort = {
else {
return 0;
}
- }
+ },
+
+ /**
+ Performs a natural-order comparison of two strings or numbers (or a string
+ and a number). This ensures that a value like 'foo2' will be sorted before
+ 'foo10', whereas a standard ASCII sort would sort 'foo10' first.
+
+ @example
+
+ var items = ['item10', 'item2', 'item1', 10, '1', 2];
+
+ items.sort(Y.ArraySort.naturalCompare);
+ console.log(items); // => ['1', 2, 10, 'item1', 'item2', 'item10']
+
+ @method naturalCompare
+ @param {Number|String} a First value to compare.
+ @param {Number|String} b Second value to compare.
+ @param {Object} [options] Options.
+ @param {Boolean} [options.caseSensitive=false] If `true`, a
+ case-sensitive comparison will be performed. By default the
+ comparison is case-insensitive.
+ @param {Boolean} [options.descending=false] If `true`, the sort order
+ will be reversed so that larger values are sorted before smaller
+ values.
+ @return {Number} `0` if the two items are equal, a negative number if _a_
+ should be sorted before _b_, or a positive number if _b_ should be
+ sorted before _a_.
+ @static
+ @since @SINCE@
+ **/
+ naturalCompare: function (a, b, options) {
+ // Coerce `a` and `b` to strings.
+ a += '';
+ b += '';
+
+ // Convert `a` and `b` to lowercase unless `options.caseSensitive` is
+ // truthy.
+ if (!options || !options.caseSensitive) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ }
+
+ // Split `a` and `b` into alpha parts and numeric parts.
+ var aParts = ArraySort._splitAlphaNum(a),
+ bParts = ArraySort._splitAlphaNum(b),
+ length = Math.min(aParts.length, bParts.length),
+ result = 0,
+
+ aPart,
+ bPart,
+ i;
+
+ // Compare each part of `a` with each part of `b`.
+ for (i = 0; i < length; i++) {
+ aPart = aParts[i];
+ bPart = bParts[i];
+
+ // If the two parts aren't equal, compare them and stop iterating.
+ if (aPart !== bPart) {
+ // First, try comparing them as numbers.
+ result = aPart - bPart;
+ // If that didn't work, compare them as strings. This falsiness
+ // check works because `result` can't be 0 (we checked for
+ // equality above) and NaN is falsy.
+ if (!result) {
+ result = aPart > bPart ? 1 : -1;
+ }
+
+ // At this point we know enough to be able to sort the two
+ // strings, so we don't need to compare any more parts.
+ break;
+ }
+ }
+
+ // If we get here and `result` is still 0, then sort the shorter string
+ // before the longer string.
+ result || (result = a.length - b.length);
+
+ // Return the result, flipping the order if `options.descending` is
+ // truthy.
+ return options && options.descending ? -result : result;
+ },
+
+ // -- Protected Methods ----------------------------------------------------
+
+ /**
+ Splits a string into an array of alpha character and digit character parts.
+
+ @example
+
+ Y.ArraySort._splitAlphaNum('abc123def456');
+ // => ['abc', '123', 'def', '456']
+
+ @method _splitAlphaNum
+ @param {String} string String to split.
+ @return {String[]} Array of alpha parts and digit parts.
+ @protected
+ @static
+ @since @SINCE@
+ **/
+ _splitAlphaNum: function (string) {
+ /*jshint boss:true */
+ var parts = [],
+ regex = /(\d+|\D+)/g,
+ match;
+
+ while (match = regex.exec(string)) { // assignment
+ parts.push(match[1]);
+ }
+
+ return parts;
+ }
};
diff --git a/build/arraysort/arraysort-min.js b/build/arraysort/arraysort-min.js
index c4f8aa82528..f0222471dfd 100644
--- a/build/arraysort/arraysort-min.js
+++ b/build/arraysort/arraysort-min.js
@@ -1 +1 @@
-YUI.add("arraysort",function(e,t){var n=e.Lang,r=n.isValue,i=n.isString;e.ArraySort={compare:function(e,t,n){return r(e)?r(t)?(i(e)&&(e=e.toLowerCase()),i(t)&&(t=t.toLowerCase()),et?n?-1:1:0):-1:r(t)?1:0}}},"@VERSION@",{requires:["yui-base"]});
+YUI.add("arraysort",function(e,t){var n=e.Lang,r=n.isValue,i=n.isString,s=e.ArraySort={compare:function(e,t,n){return r(e)?r(t)?(i(e)&&(e=e.toLowerCase()),i(t)&&(t=t.toLowerCase()),et?n?-1:1:0):-1:r(t)?1:0},naturalCompare:function(e,t,n){e+="",t+="";if(!n||!n.caseSensitive)e=e.toLowerCase(),t=t.toLowerCase();var r=s._splitAlphaNum(e),i=s._splitAlphaNum(t),o=Math.min(r.length,i.length),u=0,a,f,l;for(l=0;lf?1:-1);break}}return u||(u=e.length-t.length),n&&n.descending?-u:u},_splitAlphaNum:function(e){var t=[],n=/(\d+|\D+)/g,r;while(r=n.exec(e))t.push(r[1]);return t}}},"@VERSION@",{requires:["yui-base"]});
diff --git a/build/arraysort/arraysort.js b/build/arraysort/arraysort.js
index 9e6fa76a23a..745fde44de2 100644
--- a/build/arraysort/arraysort.js
+++ b/build/arraysort/arraysort.js
@@ -1,22 +1,26 @@
YUI.add('arraysort', function (Y, NAME) {
+/*jshint expr:true, onevar:false */
+
/**
-Provides a case-insenstive comparator which can be used for array sorting.
+Provides comparator functions useful for sorting arrays.
@module arraysort
-*/
+**/
var LANG = Y.Lang,
ISVALUE = LANG.isValue,
ISSTRING = LANG.isString;
/**
-Provides a case-insenstive comparator which can be used for array sorting.
+Provides comparator functions useful for sorting arrays.
@class ArraySort
-*/
+@static
+**/
-Y.ArraySort = {
+var ArraySort = Y.ArraySort = {
+ // -- Public Methods -------------------------------------------------------
/**
Comparator function for simple case-insensitive sorting of an array of
@@ -28,6 +32,7 @@ Y.ArraySort = {
@param desc {Boolean} `true` if sort direction is descending, `false` if
sort direction is ascending.
@return {Boolean} -1 when a < b. 0 when a == b. 1 when a > b.
+ @static
*/
compare: function(a, b, desc) {
if(!ISVALUE(a)) {
@@ -57,8 +62,119 @@ Y.ArraySort = {
else {
return 0;
}
- }
+ },
+
+ /**
+ Performs a natural-order comparison of two strings or numbers (or a string
+ and a number). This ensures that a value like 'foo2' will be sorted before
+ 'foo10', whereas a standard ASCII sort would sort 'foo10' first.
+
+ @example
+
+ var items = ['item10', 'item2', 'item1', 10, '1', 2];
+
+ items.sort(Y.ArraySort.naturalCompare);
+ console.log(items); // => ['1', 2, 10, 'item1', 'item2', 'item10']
+
+ @method naturalCompare
+ @param {Number|String} a First value to compare.
+ @param {Number|String} b Second value to compare.
+ @param {Object} [options] Options.
+ @param {Boolean} [options.caseSensitive=false] If `true`, a
+ case-sensitive comparison will be performed. By default the
+ comparison is case-insensitive.
+ @param {Boolean} [options.descending=false] If `true`, the sort order
+ will be reversed so that larger values are sorted before smaller
+ values.
+ @return {Number} `0` if the two items are equal, a negative number if _a_
+ should be sorted before _b_, or a positive number if _b_ should be
+ sorted before _a_.
+ @static
+ @since @SINCE@
+ **/
+ naturalCompare: function (a, b, options) {
+ // Coerce `a` and `b` to strings.
+ a += '';
+ b += '';
+
+ // Convert `a` and `b` to lowercase unless `options.caseSensitive` is
+ // truthy.
+ if (!options || !options.caseSensitive) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ }
+
+ // Split `a` and `b` into alpha parts and numeric parts.
+ var aParts = ArraySort._splitAlphaNum(a),
+ bParts = ArraySort._splitAlphaNum(b),
+ length = Math.min(aParts.length, bParts.length),
+ result = 0,
+
+ aPart,
+ bPart,
+ i;
+
+ // Compare each part of `a` with each part of `b`.
+ for (i = 0; i < length; i++) {
+ aPart = aParts[i];
+ bPart = bParts[i];
+
+ // If the two parts aren't equal, compare them and stop iterating.
+ if (aPart !== bPart) {
+ // First, try comparing them as numbers.
+ result = aPart - bPart;
+ // If that didn't work, compare them as strings. This falsiness
+ // check works because `result` can't be 0 (we checked for
+ // equality above) and NaN is falsy.
+ if (!result) {
+ result = aPart > bPart ? 1 : -1;
+ }
+
+ // At this point we know enough to be able to sort the two
+ // strings, so we don't need to compare any more parts.
+ break;
+ }
+ }
+
+ // If we get here and `result` is still 0, then sort the shorter string
+ // before the longer string.
+ result || (result = a.length - b.length);
+
+ // Return the result, flipping the order if `options.descending` is
+ // truthy.
+ return options && options.descending ? -result : result;
+ },
+
+ // -- Protected Methods ----------------------------------------------------
+
+ /**
+ Splits a string into an array of alpha character and digit character parts.
+
+ @example
+
+ Y.ArraySort._splitAlphaNum('abc123def456');
+ // => ['abc', '123', 'def', '456']
+
+ @method _splitAlphaNum
+ @param {String} string String to split.
+ @return {String[]} Array of alpha parts and digit parts.
+ @protected
+ @static
+ @since @SINCE@
+ **/
+ _splitAlphaNum: function (string) {
+ /*jshint boss:true */
+ var parts = [],
+ regex = /(\d+|\D+)/g,
+ match;
+
+ while (match = regex.exec(string)) { // assignment
+ parts.push(match[1]);
+ }
+
+ return parts;
+ }
};
From 0c80188dc92b5debb5c3ee631fc72a54edf930ee Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Mon, 20 May 2013 10:17:46 -0700
Subject: [PATCH 12/19] Add missing unit tests.
---
src/common/tests/unit.xml | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/common/tests/unit.xml b/src/common/tests/unit.xml
index eabe15de3c1..4ed00a05526 100644
--- a/src/common/tests/unit.xml
+++ b/src/common/tests/unit.xml
@@ -25,9 +25,11 @@
charts/tests/unit/area-dataprovider.html
charts/tests/unit/area-globalstyles.html
charts/tests/unit/area-gridlines.html
+ charts/tests/unit/area-legend-styles.html
charts/tests/unit/area-legend.html
charts/tests/unit/area-legendwrapitems.html
charts/tests/unit/areaspline-gridlines.html
+ charts/tests/unit/areaspline-legend-styles.html
charts/tests/unit/areaspline-legend.html
charts/tests/unit/areaspline-legendwrapitems.html
charts/tests/unit/ariaeventtests.html
@@ -52,6 +54,7 @@
charts/tests/unit/bar-dataprovider.html
charts/tests/unit/bar-globalstyles.html
charts/tests/unit/bar-gridlines.html
+ charts/tests/unit/bar-legend-styles.html
charts/tests/unit/bar-legend.html
charts/tests/unit/bar-legendwrapitems.html
charts/tests/unit/charts-domevents.html
@@ -59,15 +62,18 @@
charts/tests/unit/column-dataprovider.html
charts/tests/unit/column-globalstyles.html
charts/tests/unit/column-gridlines.html
+ charts/tests/unit/column-legend-styles.html
charts/tests/unit/column-legend.html
charts/tests/unit/column-legendwrapitems.html
charts/tests/unit/combo-globalstyles.html
charts/tests/unit/combo-gridlines.html
+ charts/tests/unit/combo-legend-styles.html
charts/tests/unit/combo-legendwrapitems.html
charts/tests/unit/combo-tooltip.html
charts/tests/unit/comboshowareafill-globalstyles.html
charts/tests/unit/combospline-globalstyles.html
charts/tests/unit/combospline-gridlines.html
+ charts/tests/unit/combospline-legend-styles.html
charts/tests/unit/combospline-legend.html
charts/tests/unit/combospline-legendwrapitems.html
charts/tests/unit/combosplineshowareafill-globalstyles.html
@@ -78,6 +84,7 @@
charts/tests/unit/line-globalstyles.html
charts/tests/unit/line-legend.html
charts/tests/unit/line-legendwrapitems.html
+ charts/tests/unit/marker-legend-styles.html
charts/tests/unit/numericaxismaxtests.html
charts/tests/unit/numericaxisminandmaxtests.html
charts/tests/unit/numericaxismintests.html
@@ -207,6 +214,7 @@
event/tests/unit/focusblur.html
gesture-simulate/tests/unit/gesture-simulate.html
get/tests/unit/get.html
+ get/tests/unit/load.html
graphics/tests/unit/drawing.html
graphics/tests/unit/graphic.html
graphics/tests/unit/graphics.html
@@ -224,8 +232,6 @@
imageloader/tests/unit/imageloader.html
intl/tests/unit/intl.html
json/tests/unit/json.html
- jsonp/tests/unit/jsonp.html
- jsonp/tests/unit/jsonp-url.html
loader/tests/unit/index-full.html
loader/tests/unit/index-loader.html
loader/tests/unit/index-static.html
@@ -251,6 +257,7 @@
scrollview/tests/unit/scrollview-base-unit-test.html
scrollview/tests/unit/scrollview-list-unit-test.html
scrollview/tests/unit/scrollview-paginator-unit-test.html
+ scrollview/tests/unit/scrollview-scrollbars-unit-test.html
simpleyui/tests/unit/simpleyui.html
slider/tests/unit/slider.html
sortable/tests/unit/sortable.html
From bdf3fad9eca7ea57c596d00bc240b43dc8fcae8b Mon Sep 17 00:00:00 2001
From: Ryan Grove
Date: Tue, 21 May 2013 12:36:36 -0700
Subject: [PATCH 13/19] ScrollInfo: Use getBoundingClientRect() instead of
DOM.getXY() for node coordinates. Fixes #767
Also improves performance significantly.
---
src/node-scroll-info/HISTORY.md | 13 ++
src/node-scroll-info/js/node-scroll-info.js | 134 +++++-------
.../meta/node-scroll-info.json | 7 +-
.../tests/manual/test-body.html | 15 +-
.../tests/manual/test-div.html | 204 ++++++++++++++++++
.../unit/assets/node-scroll-info-test.js | 107 ++++++++-
.../tests/unit/node-scroll-info.html | 11 +-
7 files changed, 387 insertions(+), 104 deletions(-)
create mode 100644 src/node-scroll-info/tests/manual/test-div.html
diff --git a/src/node-scroll-info/HISTORY.md b/src/node-scroll-info/HISTORY.md
index bbdd65f5e76..d6aaf880a13 100644
--- a/src/node-scroll-info/HISTORY.md
+++ b/src/node-scroll-info/HISTORY.md
@@ -1,6 +1,19 @@
ScrollInfo Node Plugin Change History
=====================================
+@VERSION@
+------
+
+* Added an `isNodeOnscreen()` method that returns `true` if the given node is
+ within the visible bounds of the viewport, `false` otherwise. [Ryan Grove]
+
+* Improved the performance of `getOffscreenNodes()` and `getOnscreenNodes()`.
+ [Ryan Grove]
+
+* Fixed a bug that caused `getOffscreenNodes()` and `getOnscreenNodes()` to
+ return incorrect information when used on a scrollable node rather than the
+ body. [Ryan Grove]
+
3.10.1
------
diff --git a/src/node-scroll-info/js/node-scroll-info.js b/src/node-scroll-info/js/node-scroll-info.js
index 2e58e5ef346..a3fb5e5b81e 100644
--- a/src/node-scroll-info/js/node-scroll-info.js
+++ b/src/node-scroll-info/js/node-scroll-info.js
@@ -176,8 +176,8 @@ Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
},
destructor: function () {
- (new Y.EventHandle(this._events)).detach();
- delete this._events;
+ new Y.EventHandle(this._events).detach();
+ this._events = null;
},
// -- Public Methods -------------------------------------------------------
@@ -202,45 +202,11 @@ Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
margin = this._scrollMargin;
}
- var lastScroll = this._lastScroll,
- nodes = this._host.all(selector || '*'),
+ var elements = Y.Selector.query(selector || '*', this._host._node);
- scrollBottom = lastScroll.scrollBottom + margin,
- scrollLeft = lastScroll.scrollLeft - margin,
- scrollRight = lastScroll.scrollRight + margin,
- scrollTop = lastScroll.scrollTop - margin,
-
- self = this;
-
- return nodes.filter(function (el) {
- var xy = Y.DOM.getXY(el),
- elLeft = xy[0] - self._left,
- elTop = xy[1] - self._top,
- elBottom, elRight;
-
- // Check whether the element's top left point is within the
- // viewport. This is the least expensive check.
- if (elLeft >= scrollLeft && elLeft < scrollRight &&
- elTop >= scrollTop && elTop < scrollBottom) {
-
- return false;
- }
-
- // Check whether the element's bottom right point is within the
- // viewport. This check is more expensive since we have to get the
- // element's height and width.
- elBottom = elTop + el.offsetHeight;
- elRight = elLeft + el.offsetWidth;
-
- if (elRight < scrollRight && elRight >= scrollLeft &&
- elBottom < scrollBottom && elBottom >= scrollTop) {
-
- return false;
- }
-
- // If we get here, the element isn't within the viewport.
- return true;
- });
+ return new Y.NodeList(Y.Array.filter(elements, function (el) {
+ return !this._isElementOnscreen(el, margin);
+ }, this));
},
/**
@@ -263,45 +229,11 @@ Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
margin = this._scrollMargin;
}
- var lastScroll = this._lastScroll,
- nodes = this._host.all(selector || '*'),
-
- scrollBottom = lastScroll.scrollBottom + margin,
- scrollLeft = lastScroll.scrollLeft - margin,
- scrollRight = lastScroll.scrollRight + margin,
- scrollTop = lastScroll.scrollTop - margin,
-
- self = this;
+ var elements = Y.Selector.query(selector || '*', this._host._node);
- return nodes.filter(function (el) {
- var xy = Y.DOM.getXY(el),
- elLeft = xy[0] - self._left,
- elTop = xy[1] - self._top,
- elBottom, elRight;
-
- // Check whether the element's top left point is within the
- // viewport. This is the least expensive check.
- if (elLeft >= scrollLeft && elLeft < scrollRight &&
- elTop >= scrollTop && elTop < scrollBottom) {
-
- return true;
- }
-
- // Check whether the element's bottom right point is within the
- // viewport. This check is more expensive since we have to get the
- // element's height and width.
- elBottom = elTop + el.offsetHeight;
- elRight = elLeft + el.offsetWidth;
-
- if (elRight < scrollRight && elRight >= scrollLeft &&
- elBottom < scrollBottom && elBottom >= scrollTop) {
-
- return true;
- }
-
- // If we get here, the element isn't within the viewport.
- return false;
- });
+ return new Y.NodeList(Y.Array.filter(elements, function (el) {
+ return this._isElementOnscreen(el, margin);
+ }, this));
},
/**
@@ -348,6 +280,24 @@ Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
};
},
+ /**
+ Returns `true` if _node_ is at least partially onscreen within the host
+ node, `false` otherwise.
+
+ @method isNodeOnscreen
+ @param {HTMLElement|Node|String} node Node or selector to check.
+ @param {Number} [margin] Additional margin in pixels beyond the actual
+ onscreen region that should be considered "onscreen" for the purposes of
+ this query. Defaults to the value of the `scrollMargin` attribute.
+ @return {Boolean} `true` if _node_ is at least partially onscreen within the
+ host node, `false` otherwise.
+ @since @SINCE@
+ **/
+ isNodeOnscreen: function (node, margin) {
+ node = Y.one(node);
+ return !!(node && this._isElementOnscreen(node._node, margin));
+ },
+
/**
Refreshes cached position, height, and width dimensions for the host node.
If the host node is the body, then the viewport height and width will be
@@ -431,6 +381,31 @@ Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
Y.Node.getDOMNode(this._host);
},
+ /**
+ Underlying element-based implementation for `isNodeOnscreen()`.
+
+ @method _isElementOnscreen
+ @param {HTMLElement} el HTML element.
+ @param {Number} [margin] Additional margin in pixels beyond the actual
+ onscreen region that should be considered "onscreen" for the purposes of
+ this query. Defaults to the value of the `scrollMargin` attribute.
+ @return {Boolean} `true` if _el_ is at least partially onscreen within the
+ host node, `false` otherwise.
+ @since @SINCE@
+ **/
+ _isElementOnscreen: function (el, margin) {
+ var rect = el.getBoundingClientRect();
+
+ if (typeof margin === 'undefined') {
+ margin = this._scrollMargin;
+ }
+
+ return (rect.top < this._height + margin
+ && rect.bottom >= -margin
+ && rect.right >= -margin
+ && rect.left < this._width + margin);
+ },
+
/**
Mixes detailed scroll information into the given DOM `scroll` event facade
and fires appropriate local events.
@@ -487,10 +462,9 @@ Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
Handles browser resize events.
@method _afterResize
- @param {EventFacade} e
@protected
**/
- _afterResize: function (e) {
+ _afterResize: function () {
this.refreshDimensions();
},
diff --git a/src/node-scroll-info/meta/node-scroll-info.json b/src/node-scroll-info/meta/node-scroll-info.json
index 93195e4d065..d0c39781a95 100644
--- a/src/node-scroll-info/meta/node-scroll-info.json
+++ b/src/node-scroll-info/meta/node-scroll-info.json
@@ -1,7 +1,12 @@
{
"node-scroll-info": {
"requires": [
- "base-build", "dom-screen", "event-resize", "node-pluginhost", "plugin"
+ "array-extras",
+ "base-build",
+ "event-resize",
+ "node-pluginhost",
+ "plugin",
+ "selector"
]
}
}
diff --git a/src/node-scroll-info/tests/manual/test-body.html b/src/node-scroll-info/tests/manual/test-body.html
index fbdbbe526e6..0d35f3a4b25 100644
--- a/src/node-scroll-info/tests/manual/test-body.html
+++ b/src/node-scroll-info/tests/manual/test-body.html
@@ -79,7 +79,7 @@
+
+
+
+