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