Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / core / src / util / Format.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.util.Format
17
18 This class is a centralized place for formatting functions inside the library. It includes
19 functions to format various different types of data, such as text, dates and numeric values.
20
21 __Localization__
22 This class contains several options for localization. These can be set once the library has loaded,
23 all calls to the functions from that point will use the locale settings that were specified.
24 Options include:
25 - thousandSeparator
26 - decimalSeparator
27 - currenyPrecision
28 - currencySign
29 - currencyAtEnd
30 This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}.
31
32 __Using with renderers__
33 There are two helper functions that return a new function that can be used in conjunction with 
34 grid renderers:
35
36     columns: [{
37         dataIndex: 'date',
38         renderer: Ext.util.Format.dateRenderer('Y-m-d')
39     }, {
40         dataIndex: 'time',
41         renderer: Ext.util.Format.numberRenderer('0.000')
42     }]
43     
44 Functions that only take a single argument can also be passed directly:
45     columns: [{
46         dataIndex: 'cost',
47         renderer: Ext.util.Format.usMoney
48     }, {
49         dataIndex: 'productCode',
50         renderer: Ext.util.Format.uppercase
51     }]
52     
53 __Using with XTemplates__
54 XTemplates can also directly use Ext.util.Format functions:
55
56     new Ext.XTemplate([
57         'Date: {startDate:date("Y-m-d")}',
58         'Cost: {cost:usMoney}'
59     ]);
60
61  * @markdown
62  * @singleton
63  */
64 (function() {
65     Ext.ns('Ext.util');
66
67     Ext.util.Format = {};
68     var UtilFormat     = Ext.util.Format,
69         stripTagsRE    = /<\/?[^>]+>/gi,
70         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
71         nl2brRe        = /\r?\n/g,
72
73         // A RegExp to remove from a number format string, all characters except digits and '.'
74         formatCleanRe  = /[^\d\.]/g,
75
76         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
77         // Created on first use. The local decimal separator character must be initialized for this to be created.
78         I18NFormatCleanRe;
79
80     Ext.apply(UtilFormat, {
81         /**
82          * @type String
83          * @property thousandSeparator
84          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
85          * <p>This defaults to <code>,</code>, but may be overridden in a locale file.</p>
86          */
87         thousandSeparator: ',',
88
89         /**
90          * @type String
91          * @property decimalSeparator
92          * <p>The character that the {@link #number} function uses as a decimal point.</p>
93          * <p>This defaults to <code>.</code>, but may be overridden in a locale file.</p>
94          */
95         decimalSeparator: '.',
96
97         /**
98          * @type Number
99          * @property currencyPrecision
100          * <p>The number of decimal places that the {@link #currency} function displays.</p>
101          * <p>This defaults to <code>2</code>, but may be overridden in a locale file.</p>
102          */
103         currencyPrecision: 2,
104
105         /**
106          * @type String
107          * @property currencySign
108          * <p>The currency sign that the {@link #currency} function displays.</p>
109          * <p>This defaults to <code>$</code>, but may be overridden in a locale file.</p>
110          */
111         currencySign: '$',
112
113         /**
114          * @type Boolean
115          * @property currencyAtEnd
116          * <p>This may be set to <code>true</code> to make the {@link #currency} function
117          * append the currency sign to the formatted value.</p>
118          * <p>This defaults to <code>false</code>, but may be overridden in a locale file.</p>
119          */
120         currencyAtEnd: false,
121
122         /**
123          * Checks a reference and converts it to empty string if it is undefined
124          * @param {Mixed} value Reference to check
125          * @return {Mixed} Empty string if converted, otherwise the original value
126          */
127         undef : function(value) {
128             return value !== undefined ? value : "";
129         },
130
131         /**
132          * Checks a reference and converts it to the default value if it's empty
133          * @param {Mixed} value Reference to check
134          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
135          * @return {String}
136          */
137         defaultValue : function(value, defaultValue) {
138             return value !== undefined && value !== '' ? value : defaultValue;
139         },
140
141         /**
142          * Returns a substring from within an original string
143          * @param {String} value The original text
144          * @param {Number} start The start index of the substring
145          * @param {Number} length The length of the substring
146          * @return {String} The substring
147          */
148         substr : function(value, start, length) {
149             return String(value).substr(start, length);
150         },
151
152         /**
153          * Converts a string to all lower case letters
154          * @param {String} value The text to convert
155          * @return {String} The converted text
156          */
157         lowercase : function(value) {
158             return String(value).toLowerCase();
159         },
160
161         /**
162          * Converts a string to all upper case letters
163          * @param {String} value The text to convert
164          * @return {String} The converted text
165          */
166         uppercase : function(value) {
167             return String(value).toUpperCase();
168         },
169
170         /**
171          * Format a number as US currency
172          * @param {Number/String} value The numeric value to format
173          * @return {String} The formatted currency string
174          */
175         usMoney : function(v) {
176             return UtilFormat.currency(v, '$', 2);
177         },
178
179         /**
180          * Format a number as a currency
181          * @param {Number/String} value The numeric value to format
182          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
183          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
184          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
185          * @return {String} The formatted currency string
186          */
187         currency: function(v, currencySign, decimals, end) {
188             var negativeSign = '',
189                 format = ",0",
190                 i = 0;
191             v = v - 0;
192             if (v < 0) {
193                 v = -v;
194                 negativeSign = '-';
195             }
196             decimals = decimals || UtilFormat.currencyPrecision;
197             format += format + (decimals > 0 ? '.' : '');
198             for (; i < decimals; i++) {
199                 format += '0';
200             }
201             v = UtilFormat.number(v, format); 
202             if ((end || UtilFormat.currencyAtEnd) === true) {
203                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
204             } else {
205                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
206             }
207         },
208
209         /**
210          * Formats the passed date using the specified format pattern.
211          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
212          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
213          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
214          * @return {String} The formatted date string.
215          */
216         date: function(v, format) {
217             if (!v) {
218                 return "";
219             }
220             if (!Ext.isDate(v)) {
221                 v = new Date(Date.parse(v));
222             }
223             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
224         },
225
226         /**
227          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
228          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
229          * @return {Function} The date formatting function
230          */
231         dateRenderer : function(format) {
232             return function(v) {
233                 return UtilFormat.date(v, format);
234             };
235         },
236
237         /**
238          * Strips all HTML tags
239          * @param {Mixed} value The text from which to strip tags
240          * @return {String} The stripped text
241          */
242         stripTags : function(v) {
243             return !v ? v : String(v).replace(stripTagsRE, "");
244         },
245
246         /**
247          * Strips all script tags
248          * @param {Mixed} value The text from which to strip script tags
249          * @return {String} The stripped text
250          */
251         stripScripts : function(v) {
252             return !v ? v : String(v).replace(stripScriptsRe, "");
253         },
254
255         /**
256          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
257          * @param {Number/String} size The numeric value to format
258          * @return {String} The formatted file size
259          */
260         fileSize : function(size) {
261             if (size < 1024) {
262                 return size + " bytes";
263             } else if (size < 1048576) {
264                 return (Math.round(((size*10) / 1024))/10) + " KB";
265             } else {
266                 return (Math.round(((size*10) / 1048576))/10) + " MB";
267             }
268         },
269
270         /**
271          * It does simple math for use in a template, for example:<pre><code>
272          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
273          * </code></pre>
274          * @return {Function} A function that operates on the passed value.
275          * @method
276          */
277         math : function(){
278             var fns = {};
279
280             return function(v, a){
281                 if (!fns[a]) {
282                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
283                 }
284                 return fns[a](v);
285             };
286         }(),
287
288         /**
289          * Rounds the passed number to the required decimal precision.
290          * @param {Number/String} value The numeric value to round.
291          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
292          * @return {Number} The rounded value.
293          */
294         round : function(value, precision) {
295             var result = Number(value);
296             if (typeof precision == 'number') {
297                 precision = Math.pow(10, precision);
298                 result = Math.round(value * precision) / precision;
299             }
300             return result;
301         },
302
303         /**
304          * <p>Formats the passed number according to the passed format string.</p>
305          * <p>The number of digits after the decimal separator character specifies the number of
306          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
307          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
308          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
309          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
310          * <p><b>New to Ext4</b></p>
311          * <p>Locale-specific characters are always used in the formatted output when inserting
312          * thousand and decimal separators.</p>
313          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
314          * thousand separator, and "." as the decimal separator)</p>
315          * <p>To allow specification of format strings according to local conventions for separator characters, add
316          * the string <code>/i</code> to the end of the format string.</p>
317          * <div style="margin-left:40px">examples (123456.789):
318          * <div style="margin-left:10px">
319          * 0 - (123456) show only digits, no precision<br>
320          * 0.00 - (123456.78) show only digits, 2 precision<br>
321          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
322          * 0,000 - (123,456) show comma and digits, no precision<br>
323          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
324          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
325          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
326          * For example: 0.000,00/i
327          * </div></div>
328          * @param {Number} v The number to format.
329          * @param {String} format The way you would like to format this text.
330          * @return {String} The formatted number.
331          */
332         number: function(v, formatString) {
333             if (!formatString) {
334                 return v;
335             }
336             v = Ext.Number.from(v, NaN);
337             if (isNaN(v)) {
338                 return '';
339             }
340             var comma = UtilFormat.thousandSeparator,
341                 dec   = UtilFormat.decimalSeparator,
342                 i18n  = false,
343                 neg   = v < 0,
344                 hasComma,
345                 psplit;
346
347             v = Math.abs(v);
348
349             // The "/i" suffix allows caller to use a locale-specific formatting string.
350             // Clean the format string by removing all but numerals and the decimal separator.
351             // Then split the format string into pre and post decimal segments according to *what* the
352             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
353             if (formatString.substr(formatString.length - 2) == '/i') {
354                 if (!I18NFormatCleanRe) {
355                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
356                 }
357                 formatString = formatString.substr(0, formatString.length - 2);
358                 i18n   = true;
359                 hasComma = formatString.indexOf(comma) != -1;
360                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
361             } else {
362                 hasComma = formatString.indexOf(',') != -1;
363                 psplit = formatString.replace(formatCleanRe, '').split('.');
364             }
365
366             if (1 < psplit.length) {
367                 v = v.toFixed(psplit[1].length);
368             } else if(2 < psplit.length) {
369                 //<debug>
370                 Ext.Error.raise({
371                     sourceClass: "Ext.util.Format",
372                     sourceMethod: "number",
373                     value: v,
374                     formatString: formatString,
375                     msg: "Invalid number format, should have no more than 1 decimal"
376                 });
377                 //</debug>
378             } else {
379                 v = v.toFixed(0);
380             }
381
382             var fnum = v.toString();
383
384             psplit = fnum.split('.');
385
386             if (hasComma) {
387                 var cnum = psplit[0],
388                     parr = [],
389                     j    = cnum.length,
390                     m    = Math.floor(j / 3),
391                     n    = cnum.length % 3 || 3,
392                     i;
393
394                 for (i = 0; i < j; i += n) {
395                     if (i !== 0) {
396                         n = 3;
397                     }
398
399                     parr[parr.length] = cnum.substr(i, n);
400                     m -= 1;
401                 }
402                 fnum = parr.join(comma);
403                 if (psplit[1]) {
404                     fnum += dec + psplit[1];
405                 }
406             } else {
407                 if (psplit[1]) {
408                     fnum = psplit[0] + dec + psplit[1];
409                 }
410             }
411             
412             if (neg) {
413                 /*
414                  * Edge case. If we have a very small negative number it will get rounded to 0,
415                  * however the initial check at the top will still report as negative. Replace
416                  * everything but 1-9 and check if the string is empty to determine a 0 value.
417                  */
418                 neg = fnum.replace(/[^1-9]/g, '') !== '';
419             }
420
421             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
422         },
423
424         /**
425          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
426          * @param {String} format Any valid number format string for {@link #number}
427          * @return {Function} The number formatting function
428          */
429         numberRenderer : function(format) {
430             return function(v) {
431                 return UtilFormat.number(v, format);
432             };
433         },
434
435         /**
436          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
437          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
438          * if the value is 0 or greater than 1.
439          * @param {Number} value The value to compare against
440          * @param {String} singular The singular form of the word
441          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
442          */
443         plural : function(v, s, p) {
444             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
445         },
446
447         /**
448          * Converts newline characters to the HTML tag &lt;br/>
449          * @param {String} The string value to format.
450          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
451          */
452         nl2br : function(v) {
453             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
454         },
455
456         /**
457          * Capitalize the given string. See {@link Ext.String#capitalize}.
458          * @method
459          */
460         capitalize: Ext.String.capitalize,
461
462         /**
463          * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
464          * See {@link Ext.String#ellipsis}.
465          * @method
466          */
467         ellipsis: Ext.String.ellipsis,
468
469         /**
470          * Formats to a string. See {@link Ext.String#format}
471          * @method
472          */
473         format: Ext.String.format,
474
475         /**
476          * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
477          * See {@link Ext.String#htmlDecode}.
478          * @method
479          */
480         htmlDecode: Ext.String.htmlDecode,
481
482         /**
483          * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
484          * See {@link Ext.String#htmlEncode}.
485          * @method
486          */
487         htmlEncode: Ext.String.htmlEncode,
488
489         /**
490          * Adds left padding to a string. See {@link Ext.String#leftPad}
491          * @method
492          */
493         leftPad: Ext.String.leftPad,
494
495         /**
496          * Trims any whitespace from either side of a string. See {@link Ext.String#trim}.
497          * @method
498          */
499         trim : Ext.String.trim,
500
501         /**
502          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
503          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
504          * @param {Number|String} v The encoded margins
505          * @return {Object} An object with margin sizes for top, right, bottom and left
506          */
507         parseBox : function(box) {
508             if (Ext.isNumber(box)) {
509                 box = box.toString();
510             }
511             var parts  = box.split(' '),
512                 ln = parts.length;
513
514             if (ln == 1) {
515                 parts[1] = parts[2] = parts[3] = parts[0];
516             }
517             else if (ln == 2) {
518                 parts[2] = parts[0];
519                 parts[3] = parts[1];
520             }
521             else if (ln == 3) {
522                 parts[3] = parts[1];
523             }
524
525             return {
526                 top   :parseInt(parts[0], 10) || 0,
527                 right :parseInt(parts[1], 10) || 0,
528                 bottom:parseInt(parts[2], 10) || 0,
529                 left  :parseInt(parts[3], 10) || 0
530             };
531         },
532
533         /**
534          * Escapes the passed string for use in a regular expression
535          * @param {String} str
536          * @return {String}
537          */
538         escapeRegex : function(s) {
539             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
540         }
541     });
542 })();
543