diff options
Diffstat (limited to 'addons/web/static/src/js/framework/utils.js')
| -rw-r--r-- | addons/web/static/src/js/framework/utils.js | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/addons/web/static/src/js/framework/utils.js b/addons/web/static/src/js/framework/utils.js new file mode 100644 index 00000000000..d412a17bc9a --- /dev/null +++ b/addons/web/static/src/js/framework/utils.js @@ -0,0 +1,383 @@ +odoo.define('web.utils', ['web.Class', 'web.translation'], function (require) { +"use strict"; + +var Class = require('web.Class'); +var translation = require('web.translation'); + +var _t = translation._t; + +/** + * computes (Math.floor(a/b), a%b and passes that to the callback. + * + * returns the callback's result + */ +function divmod (a, b, fn) { + var mod = a%b; + // in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a + // round of b + if (mod > 0 && b < 0 || mod < 0 && b > 0) { + mod += b; + } + return fn(Math.floor(a/b), mod); +} + +/** + * Passes the fractional and integer parts of x to the callback, returns + * the callback's result + */ +function modf (x, fn) { + var mod = x%1; + if (mod < 0) { + mod += 1; + } + return fn(mod, Math.floor(x)); +} + +/* + * Left-pad provided arg 1 with zeroes until reaching size provided by second + * argument. + * + * @param {Number|String} str value to pad + * @param {Number} size size to reach on the final padded value + * @returns {String} padded string + */ +function lpad (str, size) { + str = "" + str; + return new Array(size - str.length + 1).join('0') + str; +} + +function rpad (str, size) { + str = "" + str; + return str + new Array(size - str.length + 1).join('0'); +} + +var id = -1; + +function generate_id () { + return ++id; +} + +/** + * Intersperses ``separator`` in ``str`` at the positions indicated by + * ``indices``. + * + * ``indices`` is an array of relative offsets (from the previous insertion + * position, starting from the end of the string) at which to insert + * ``separator``. + * + * There are two special values: + * + * ``-1`` + * indicates the insertion should end now + * ``0`` + * indicates that the previous section pattern should be repeated (until all + * of ``str`` is consumed) + * + * @param {String} str + * @param {Array<Number>} indices + * @param {String} separator + * @returns {String} + */ +function intersperse (str, indices, separator) { + separator = separator || ''; + var result = [], last = str.length; + + for(var i=0; i<indices.length; ++i) { + var section = indices[i]; + if (section === -1 || last <= 0) { + // Done with string, or -1 (stops formatting string) + break; + } else if(section === 0 && i === 0) { + // repeats previous section, which there is none => stop + break; + } else if (section === 0) { + // repeat previous section forever + //noinspection AssignmentToForLoopParameterJS + section = indices[--i]; + } + result.push(str.substring(last-section, last)); + last -= section; + } + + var s = str.substring(0, last); + if (s) { result.push(s); } + return result.reverse().join(separator); +} + +function xml_to_json (node, strip_whitespace) { + switch (node.nodeType) { + case 9: + return xml_to_json(node.documentElement, strip_whitespace); + case 3: + case 4: + return (strip_whitespace && node.data.trim() === '') ? undefined : node.data; + case 1: + var attrs = $(node).getAttributes(); + _.each(['domain', 'filter_domain', 'context', 'default_get'], function(key) { + if (attrs[key]) { + try { + attrs[key] = JSON.parse(attrs[key]); + } catch(e) { } + } + }); + return { + tag: node.tagName.toLowerCase(), + attrs: attrs, + children: _.compact(_.map(node.childNodes, function(node) { + return xml_to_json(node, strip_whitespace); + })), + }; + } +} + +function json_node_to_xml (node, human_readable, indent) { + // For debugging purpose, this function will convert a json node back to xml + indent = indent || 0; + var sindent = (human_readable ? (new Array(indent + 1).join('\t')) : ''), + r = sindent + '<' + node.tag, + cr = human_readable ? '\n' : ''; + + if (typeof(node) === 'string') { + return sindent + node; + } else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) { + throw new Error( + _.str.sprintf(_t("Node [%s] is not a JSONified XML node"), + JSON.stringify(node))); + } + for (var attr in node.attrs) { + var vattr = node.attrs[attr]; + if (typeof(vattr) !== 'string') { + // domains, ... + vattr = JSON.stringify(vattr); + } + vattr = vattr.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); + if (human_readable) { + vattr = vattr.replace(/"/g, "'"); + } + r += ' ' + attr + '="' + vattr + '"'; + } + if (node.children && node.children.length) { + r += '>' + cr; + var childs = []; + for (var i = 0, ii = node.children.length; i < ii; i++) { + childs.push(json_node_to_xml(node.children[i], human_readable, indent + 1)); + } + r += childs.join(cr); + r += cr + sindent + '</' + node.tag + '>'; + return r; + } else { + return r + '/>'; + } +} + +function xml_to_str (node) { + var str = ""; + if (window.XMLSerializer) { + str = (new XMLSerializer()).serializeToString(node); + } else if (window.ActiveXObject) { + str = node.xml; + } else { + throw new Error(_t("Could not serialize XML")); + } + // Browsers won't deal with self closing tags except void elements: + // http://www.w3.org/TR/html-markup/syntax.html + var void_elements = 'area base br col command embed hr img input keygen link meta param source track wbr'.split(' '); + + // The following regex is a bit naive but it's ok for the xmlserializer output + str = str.replace(/<([a-z]+)([^<>]*)\s*\/\s*>/g, function(match, tag, attrs) { + if (void_elements.indexOf(tag) < 0) { + return "<" + tag + attrs + "></" + tag + ">"; + } else { + return match; + } + }); + return str; +} + +function Mutex () { + this.def = $.Deferred().resolve(); +} + +Mutex.prototype.exec = function (action) { + var current = this.def; + var next = this.def = $.Deferred(); + return current.then(function() { + return $.when(action()).always(function() { + next.resolve(); + }); + }); +}; + +function get_cookie (c_name) { + var cookies = document.cookie ? document.cookie.split('; ') : []; + for (var i = 0, l = cookies.length; i < l; i++) { + var parts = cookies[i].split('='); + var name = parts.shift(); + var cookie = parts.join('='); + + if (c_name && c_name === name) { + return cookie; + } + } + return ""; +} + +/** + * Insert "thousands" separators in the provided number (which is actually + * a string) + * + * @param {String} num + * @returns {String} + */ +function insert_thousand_seps (num) { + var negative = num[0] === '-'; + num = (negative ? num.slice(1) : num); + return (negative ? '-' : '') + intersperse( + num, _t.database.parameters.grouping, _t.database.parameters.thousands_sep); +} + +function is_bin_size (v) { + return (/^\d+(\.\d*)? \w+$/).test(v); +} + +/** + * Check with a scary heuristic if the value is a bin_size or not. + * If not, compute an approximate size out of the base64 encoded string. + * + * @param {String} value original format + */ +function binary_to_binsize (value) { + if (!value) { + return human_size(0); + } + if (value.substr(0, 10).indexOf(' ') == -1) { + // Computing approximate size out of base64 encoded string + // http://en.wikipedia.org/wiki/Base64#MIME + return human_size(value.length / 1.37); + } else { + // already bin_size + return value; + } +} + +/** + * Returns a human readable size + * + * @param {Number} numner of bytes + */ +function human_size (size) { + var units = _t("Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb").split(','); + var i = 0; + while (size >= 1024) { + size /= 1024; + ++i; + } + return size.toFixed(2) + ' ' + units[i]; +} + +/** + * performs a half up rounding with arbitrary precision, correcting for float loss of precision + * See the corresponding float_round() in server/tools/float_utils.py for more info + * @param {Number} the value to be rounded + * @param {Number} a precision parameter. eg: 0.01 rounds to two digits. + */ +function round_precision (value, precision) { + if (!value) { + return 0; + } else if (!precision || precision < 0) { + precision = 1; + } + var normalized_value = value / precision; + var epsilon_magnitude = Math.log(Math.abs(normalized_value))/Math.log(2); + var epsilon = Math.pow(2, epsilon_magnitude - 53); + normalized_value += normalized_value >= 0 ? epsilon : -epsilon; + var rounded_value = Math.round(normalized_value); + return rounded_value * precision; +} + +/** + * performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision + * See the corresponding float_round() in server/tools/float_utils.py for more info + * @param {Number} the value to be rounded + * @param {Number} the number of decimals. eg: round_decimals(3.141592,2) -> 3.14 + */ +function round_decimals (value, decimals) { + return round_precision(value, Math.pow(10,-decimals)); +} + +function float_is_zero (value, decimals) { + var epsilon = Math.pow(10, -decimals); + return Math.abs(round_precision(value, epsilon)) < epsilon; +}; + +function assert (bool) { + if (!bool) { + throw new Error("AssertionError"); + } +} + +var DropMisordered = Class.extend({ + /** + * @constructs instance.web.DropMisordered + * @extends instance.web.Class + * + * @param {Boolean} [failMisordered=false] whether mis-ordered responses should be failed or just ignored + */ + init: function (failMisordered) { + // local sequence number, for requests sent + this.lsn = 0; + // remote sequence number, seqnum of last received request + this.rsn = -1; + this.failMisordered = failMisordered || false; + }, + /** + * Adds a deferred (usually an async request) to the sequencer + * + * @param {$.Deferred} deferred to ensure add + * @returns {$.Deferred} + */ + add: function (deferred) { + var res = $.Deferred(); + + var self = this, seq = this.lsn++; + deferred.done(function () { + if (seq > self.rsn) { + self.rsn = seq; + res.resolve.apply(res, arguments); + } else if (self.failMisordered) { + res.reject(); + } + }).fail(function () { + res.reject.apply(res, arguments); + }); + + return res.promise(); + }, +}); + + +return { + divmod: divmod, + modf: modf, + lpad: lpad, + rpad: rpad, + generate_id: generate_id, + intersperse: intersperse, + xml_to_json: xml_to_json, + json_node_to_xml: json_node_to_xml, + xml_to_str: xml_to_str, + Mutex: Mutex, + get_cookie: get_cookie, + insert_thousand_seps: insert_thousand_seps, + is_bin_size: is_bin_size, + binary_to_binsize: binary_to_binsize, + human_size: human_size, + round_precision: round_precision, + round_decimals: round_decimals, + float_is_zero: float_is_zero, + assert: assert, + DropMisordered: DropMisordered, +}; + +}); + |
