[MERGE] Merged with main addons.
[odoo/odoo.git] / addons / web / static / src / js / formats.js
1
2 openerp.web.formats = function(instance) {
3 var _t = instance.web._t;
4
5 /**
6  * Intersperses ``separator`` in ``str`` at the positions indicated by
7  * ``indices``.
8  *
9  * ``indices`` is an array of relative offsets (from the previous insertion
10  * position, starting from the end of the string) at which to insert
11  * ``separator``.
12  *
13  * There are two special values:
14  *
15  * ``-1``
16  *   indicates the insertion should end now
17  * ``0``
18  *   indicates that the previous section pattern should be repeated (until all
19  *   of ``str`` is consumed)
20  *
21  * @param {String} str
22  * @param {Array<Number>} indices
23  * @param {String} separator
24  * @returns {String}
25  */
26 instance.web.intersperse = function (str, indices, separator) {
27     separator = separator || '';
28     var result = [], last = str.length;
29
30     for(var i=0; i<indices.length; ++i) {
31         var section = indices[i];
32         if (section === -1 || last <= 0) {
33             // Done with string, or -1 (stops formatting string)
34             break;
35         } else if(section === 0 && i === 0) {
36             // repeats previous section, which there is none => stop
37             break;
38         } else if (section === 0) {
39             // repeat previous section forever
40             //noinspection AssignmentToForLoopParameterJS
41             section = indices[--i];
42         }
43         result.push(str.substring(last-section, last));
44         last -= section;
45     }
46
47     var s = str.substring(0, last);
48     if (s) { result.push(s); }
49     return result.reverse().join(separator);
50 };
51 /**
52  * Insert "thousands" separators in the provided number (which is actually
53  * a string)
54  *
55  * @param {String} num
56  * @returns {String}
57  */
58 instance.web.insert_thousand_seps = function (num) {
59     var negative = num[0] === '-';
60     num = (negative ? num.slice(1) : num);
61     return (negative ? '-' : '') + instance.web.intersperse(
62         num, _t.database.parameters.grouping, _t.database.parameters.thousands_sep);
63 };
64
65 /**
66  * removes literal (non-format) text from a date or time pattern, as datejs can
67  * not deal with literal text in format strings (whatever the format), whereas
68  * strftime allows for literal characters
69  *
70  * @param {String} value original format
71  */
72 instance.web.strip_raw_chars = function (value) {
73     var isletter = /[a-zA-Z]/, output = [];
74     for(var index=0; index < value.length; ++index) {
75         var character = value[index];
76         if(isletter.test(character) && (index === 0 || value[index-1] !== '%')) {
77             continue;
78         }
79         output.push(character);
80     }
81     return output.join('');
82 };
83 var normalize_format = function (format) {
84     return Date.normalizeFormat(instance.web.strip_raw_chars(format));
85 };
86 /**
87  * Formats a single atomic value based on a field descriptor
88  *
89  * @param {Object} value read from OpenERP
90  * @param {Object} descriptor union of orm field and view field
91  * @param {Object} [descriptor.widget] widget to use to display the value
92  * @param {Object} descriptor.type fallback if no widget is provided, or if the provided widget is unknown
93  * @param {Object} [descriptor.digits] used for the formatting of floats
94  * @param {String} [value_if_empty=''] returned if the ``value`` argument is considered empty
95  */
96 instance.web.format_value = function (value, descriptor, value_if_empty) {
97     // If NaN value, display as with a `false` (empty cell)
98     if (typeof value === 'number' && isNaN(value)) {
99         value = false;
100     }
101     //noinspection FallthroughInSwitchStatementJS
102     switch (value) {
103         case '':
104             if (descriptor.type === 'char') {
105                 return '';
106             }
107             console.warn('Field', descriptor, 'had an empty string as value, treating as false...');
108         case false:
109         case Infinity:
110         case -Infinity:
111             return value_if_empty === undefined ?  '' : value_if_empty;
112     }
113     var l10n = _t.database.parameters;
114     switch (descriptor.widget || descriptor.type || (descriptor.field && descriptor.field.type)) {
115         case 'id':
116             return value.toString();
117         case 'integer':
118             return instance.web.insert_thousand_seps(
119                 _.str.sprintf('%d', value));
120         case 'float':
121             var digits = descriptor.digits ? descriptor.digits : [69,2];
122             digits = typeof digits === "string" ? py.eval(digits) : digits;
123             var precision = digits[1];
124             var formatted = _.str.sprintf('%.' + precision + 'f', value).split('.');
125             formatted[0] = instance.web.insert_thousand_seps(formatted[0]);
126             return formatted.join(l10n.decimal_point);
127         case 'float_time':
128             var pattern = '%02d:%02d';
129             if (value < 0) {
130                 value = Math.abs(value);
131                 pattern = '-' + pattern;
132             }
133             return _.str.sprintf(pattern,
134                     Math.floor(value),
135                     Math.round((value % 1) * 60));
136         case 'many2one':
137             // name_get value format
138             return value[1];
139         case 'one2many':
140         case 'many2many':
141             return _.str.sprintf(_t("(%d records)"), value.length);
142         case 'datetime':
143             if (typeof(value) == "string")
144                 value = instance.web.auto_str_to_date(value);
145
146             return value.toString(normalize_format(l10n.date_format)
147                         + ' ' + normalize_format(l10n.time_format));
148         case 'date':
149             if (typeof(value) == "string")
150                 value = instance.web.auto_str_to_date(value);
151             return value.toString(normalize_format(l10n.date_format));
152         case 'time':
153             if (typeof(value) == "string")
154                 value = instance.web.auto_str_to_date(value);
155             return value.toString(normalize_format(l10n.time_format));
156         case 'selection': case 'statusbar':
157             // Each choice is [value, label]
158             if(_.isArray(value)) {
159                  value = value[0]
160             }
161             var result = _(descriptor.selection).detect(function (choice) {
162                 return choice[0] === value;
163             });
164             if (result) { return result[1]; }
165             return;
166         default:
167             return value;
168     }
169 };
170
171 instance.web.parse_value = function (value, descriptor, value_if_empty) {
172     var date_pattern = normalize_format(_t.database.parameters.date_format),
173         time_pattern = normalize_format(_t.database.parameters.time_format);
174     switch (value) {
175         case false:
176         case "":
177             return value_if_empty === undefined ?  false : value_if_empty;
178     }
179     switch (descriptor.widget || descriptor.type || (descriptor.field && descriptor.field.type)) {
180         case 'integer':
181             var tmp;
182             do {
183                 tmp = value;
184                 value = value.replace(instance.web._t.database.parameters.thousands_sep, "");
185             } while(tmp !== value);
186             tmp = Number(value);
187             if (isNaN(tmp))
188                 throw new Error(value + " is not a correct integer");
189             return tmp;
190         case 'float':
191             var tmp = Number(value);
192             if (!isNaN(tmp))
193                 return tmp;
194
195             var tmp2 = value;
196             do {
197                 tmp = tmp2;
198                 tmp2 = tmp.replace(instance.web._t.database.parameters.thousands_sep, "");
199             } while(tmp !== tmp2);
200             var reformatted_value = tmp.replace(instance.web._t.database.parameters.decimal_point, ".");
201             var parsed = Number(reformatted_value);
202             if (isNaN(parsed))
203                 throw new Error(value + " is not a correct float");
204             return parsed;
205         case 'float_time':
206             var factor = 1;
207             if (value[0] === '-') {
208                 value = value.slice(1);
209                 factor = -1;
210             }
211             var float_time_pair = value.split(":");
212             if (float_time_pair.length != 2)
213                 return factor * instance.web.parse_value(value, {type: "float"});
214             var hours = instance.web.parse_value(float_time_pair[0], {type: "integer"});
215             var minutes = instance.web.parse_value(float_time_pair[1], {type: "integer"});
216             return factor * (hours + (minutes / 60));
217         case 'progressbar':
218             return instance.web.parse_value(value, {type: "float"});
219         case 'datetime':
220             var datetime = Date.parseExact(
221                     value, (date_pattern + ' ' + time_pattern));
222             if (datetime !== null)
223                 return instance.web.datetime_to_str(datetime);
224             datetime = Date.parse(value);
225             if (datetime !== null)
226                 return instance.web.datetime_to_str(datetime);
227             throw new Error(value + " is not a valid datetime");
228         case 'date':
229             var date = Date.parseExact(value, date_pattern);
230             if (date !== null)
231                 return instance.web.date_to_str(date);
232             date = Date.parse(value);
233             if (date !== null)
234                 return instance.web.date_to_str(date);
235             throw new Error(value + " is not a valid date");
236         case 'time':
237             var time = Date.parseExact(value, time_pattern);
238             if (time !== null)
239                 return instance.web.time_to_str(time);
240             time = Date.parse(value);
241             if (time !== null)
242                 return instance.web.time_to_str(time);
243             throw new Error(value + " is not a valid time");
244     }
245     return value;
246 };
247
248 instance.web.auto_str_to_date = function(value, type) {
249     try {
250         return instance.web.str_to_datetime(value);
251     } catch(e) {}
252     try {
253         return instance.web.str_to_date(value);
254     } catch(e) {}
255     try {
256         return instance.web.str_to_time(value);
257     } catch(e) {}
258     throw new Error("'" + value + "' is not a valid date, datetime nor time");
259 };
260
261 instance.web.auto_date_to_str = function(value, type) {
262     switch(type) {
263         case 'datetime':
264             return instance.web.datetime_to_str(value);
265         case 'date':
266             return instance.web.date_to_str(value);
267         case 'time':
268             return instance.web.time_to_str(value);
269         default:
270             throw new Error(type + " is not convertible to date, datetime nor time");
271     }
272 };
273
274 /**
275  * Formats a provided cell based on its field type. Most of the field types
276  * return a correctly formatted value, but some tags and fields are
277  * special-cased in their handling:
278  *
279  * * buttons will return an actual ``<button>`` tag with a bunch of error handling
280  *
281  * * boolean fields will return a checkbox input, potentially disabled
282  *
283  * * binary fields will return a link to download the binary data as a file
284  *
285  * @param {Object} row_data record whose values should be displayed in the cell
286  * @param {Object} column column descriptor
287  * @param {"button"|"field"} column.tag base control type
288  * @param {String} column.type widget type for a field control
289  * @param {String} [column.string] button label
290  * @param {String} [column.icon] button icon
291  * @param {Object} [options]
292  * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
293  * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
294  * @param {String} [options.model] current record's model
295  * @param {Number} [options.id] current record's id
296  *
297  */
298 instance.web.format_cell = function (row_data, column, options) {
299     options = options || {};
300     var attrs = {};
301     if (options.process_modifiers !== false) {
302         attrs = column.modifiers_for(row_data);
303     }
304     if (attrs.invisible) { return ''; }
305
306     if (column.tag === 'button') {
307         return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
308             '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
309             '</button>', {
310                 title: column.string || '',
311                 additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
312                     'disabled="disabled" class="oe-listview-button-disabled"' : '',
313                 prefix: instance.connection.prefix,
314                 icon: column.icon,
315                 alt: column.string || ''
316             });
317     }
318     if (!row_data[column.id]) {
319         return options.value_if_empty === undefined ? '' : options.value_if_empty;
320     }
321
322     switch (column.widget || column.type) {
323     case "boolean":
324         return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
325                  row_data[column.id].value ? 'checked="checked"' : '');
326     case "binary":
327         var text = _t("Download"),
328             download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id);
329         if (column.filename) {
330             download_url += '&filename_field=' + column.filename;
331             if (row_data[column.filename]) {
332                 text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
333                         row_data[column.filename].value, {type: 'char'}));
334             }
335         }
336         return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
337             text: text,
338             href: download_url,
339             size: row_data[column.id].value
340         });
341     case 'progressbar':
342         return _.template(
343             '<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
344                 value: _.str.sprintf("%.0f", row_data[column.id].value || 0)
345             });
346     }
347
348     return _.escape(instance.web.format_value(
349             row_data[column.id].value, column, options.value_if_empty));
350 }
351
352 };