flectra/addons/web/static/src/js/fields/field_utils.js
Siddharth Bhalgami a6c8f12991 [IMP] FireFox Side-Navbar Scroll Issue Fixed
Kanban Image Issue: If Image is unset then in kanban its showing error(NaN bytes) -- (FIX forward port from Odoo)
2018-03-23 11:25:57 +05:30

619 lines
21 KiB
JavaScript

flectra.define('web.field_utils', function (require) {
"use strict";
/**
* Field Utils
*
* This file contains two types of functions: formatting functions and parsing
* functions.
*
* Each field type has to display in string form at some point, but it should be
* stored in memory with the actual value. For example, a float value of 0.5 is
* represented as the string "0.5" but is kept in memory as a float. A date
* (or datetime) value is always stored as a Moment.js object, but displayed as
* a string. This file contains all sort of functions necessary to perform the
* conversions.
*/
var core = require('web.core');
var dom = require('web.dom');
var session = require('web.session');
var time = require('web.time');
var utils = require('web.utils');
var _t = core._t;
//------------------------------------------------------------------------------
// Formatting
//------------------------------------------------------------------------------
/**
* Convert binary to bin_size
*
* @param {string} [value] base64 representation of the binary (might be already a bin_size!)
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options (note: this parameter is ignored)
*
* @returns {string} bin_size (which is human-readable)
*/
function formatBinary(value, field, options) {
if (!value) {
return '';
}
return utils.binaryToBinsize(value);
}
/**
* @todo Really? it returns a jQuery element... We should try to avoid this and
* let DOM utility functions handle this directly. And replace this with a
* function that returns a string so we can get rid of the forceString.
*
* @param {boolean} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.forceString=false] if true, returns a string
* representation of the boolean rather than a jQueryElement
* @returns {jQuery|string}
*/
function formatBoolean(value, field, options) {
if (options && options.forceString) {
return value ? _t('True') : _t('False');
}
return dom.renderCheckbox({
prop: {
checked: value,
disabled: true,
},
});
}
/**
* Returns a string representing a char. If the value is false, then we return
* an empty string.
*
* @param {string|false} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.escape=false] if true, escapes the formatted value
* @param {boolean} [options.isPassword=false] if true, returns '********'
* instead of the formatted value
* @returns {string}
*/
function formatChar(value, field, options) {
value = typeof value === 'string' ? value : '';
if (options && options.isPassword) {
return _.str.repeat('*', value ? value.length : 0);
}
if (options && options.escape) {
value = _.escape(value);
}
return value;
}
/**
* Returns a string representing a date. If the value is false, then we return
* an empty string. Note that this is dependant on the localization settings
*
* @param {Moment|false} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.timezone=true] use the user timezone when formating the
* date
* @returns {string}
*/
function formatDate(value, field, options) {
if (value === false) {
return "";
}
if (field && field.type === 'datetime') {
if (!options || !('timezone' in options) || options.timezone) {
value = value.clone().add(session.getTZOffset(value), 'minutes');
}
}
var date_format = time.getLangDateFormat();
return value.format(date_format);
}
/**
* Returns a string representing a datetime. If the value is false, then we
* return an empty string. Note that this is dependant on the localization
* settings
*
* @params {Moment|false}
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.timezone=true] use the user timezone when formating the
* date
* @returns {string}
*/
function formatDateTime(value, field, options) {
if (value === false) {
return "";
}
if (!options || !('timezone' in options) || options.timezone) {
value = value.clone().add(session.getTZOffset(value), 'minutes');
}
return value.format(time.getLangDatetimeFormat());
}
/**
* Returns a string representing a float. The result takes into account the
* user settings (to display the correct decimal separator).
*
* @param {float|false} value the value that should be formatted
* @param {Object} [field] a description of the field (returned by fields_get
* for example). It may contain a description of the number of digits that
* should be used.
* @param {Object} [options] additional options to override the values in the
* python description of the field.
* @param {integer[]} [options.digits] the number of digits that should be used,
* instead of the default digits precision in the field.
* @returns {string}
*/
function formatFloat(value, field, options) {
if (value === false) {
return "";
}
var l10n = core._t.database.parameters;
var precision;
if (options && options.digits) {
precision = options.digits[1];
} else if (field && field.digits) {
precision = field.digits[1];
} else {
precision = 2;
}
var formatted = _.str.sprintf('%.' + precision + 'f', value || 0).split('.');
formatted[0] = utils.insert_thousand_seps(formatted[0]);
return formatted.join(l10n.decimal_point);
}
/**
* Returns a string representing a time value, from a float. The idea is that
* we sometimes want to display something like 1:45 instead of 1.75, or 0:15
* instead of 0.25.
*
* @param {float} value
* @returns {string}
*/
function formatFloatTime(value) {
var pattern = '%02d:%02d';
if (value < 0) {
value = Math.abs(value);
pattern = '-' + pattern;
}
var hour = Math.floor(value);
var min = Math.round((value % 1) * 60);
if (min === 60){
min = 0;
hour = hour + 1;
}
return _.str.sprintf(pattern, hour, min);
}
/**
* Returns a string representing an integer. If the value is false, then we
* return an empty string.
*
* @param {integer|false} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.isPassword=false] if true, returns '********'
* @returns {string}
*/
function formatInteger(value, field, options) {
if (options && options.isPassword) {
return _.str.repeat('*', String(value).length);
}
if (!value && value !== 0) {
// previously, it returned 'false'. I don't know why. But for the Pivot
// view, I want to display the concept of 'no value' with an empty
// string.
return "";
}
return utils.insert_thousand_seps(_.str.sprintf('%d', value));
}
/**
* Returns a string representing an many2one. If the value is false, then we
* return an empty string. Note that it accepts two types of input parameters:
* an array, in that case we assume that the many2one value is of the form
* [id, nameget], and we return the nameget, or it can be an object, and in that
* case, we assume that it is a record from a BasicModel.
*
* @param {Array|Object|false} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.escape=false] if true, escapes the formatted value
* @returns {string}
*/
function formatMany2one(value, field, options) {
value = value && (_.isArray(value) ? value[1] : value.data.display_name) || '';
if (options && options.escape) {
value = _.escape(value);
}
return value;
}
/**
* Returns a string indicating the number of records in the relation.
*
* @param {Object} value a valid element from a BasicModel, that represents a
* list of values
* @returns {string}
*/
function formatX2Many(value) {
if (value.data.length === 0) {
return _t('No records');
} else if (value.data.length === 1) {
return _t('1 record');
} else {
return value.data.length + _t(' records');
}
}
/**
* Returns a string representing a monetary value. The result takes into account
* the user settings (to display the correct decimal separator, currency, ...).
*
* @param {float|false} value the value that should be formatted
* @param {Object} [field]
* a description of the field (returned by fields_get for example). It
* may contain a description of the number of digits that should be used.
* @param {Object} [options]
* additional options to override the values in the python description of
* the field.
* @param {Object} [options.currency] the description of the currency to use
* @param {integer} [options.currency_id]
* the id of the 'res.currency' to use (ignored if options.currency)
* @param {string} [options.currency_field]
* the name of the field whose value is the currency id
* (ignore if options.currency or options.currency_id)
* Note: if not given it will default to the field currency_field value
* or to 'currency_id'.
* @param {Object} [options.data]
* a mapping of field name to field value, required with
* options.currency_field
* @param {integer[]} [options.digits]
* the number of digits that should be used, instead of the default
* digits precision in the field. Note: if the currency defines a
* precision, the currency's one is used.
* @returns {string}
*/
function formatMonetary(value, field, options) {
if (value === false) {
return "";
}
options = options || {};
var currency = options.currency;
if (!currency) {
var currency_id = options.currency_id;
if (!currency_id && options.data) {
var currency_field = options.currency_field || field.currency_field || 'currency_id';
currency_id = options.data[currency_field] && options.data[currency_field].res_id;
}
currency = session.get_currency(currency_id);
}
var digits = (currency && currency.digits) || options.digits;
if (options.field_digits === true) {
digits = field.digits || digits;
}
var formatted_value = formatFloat(value, field, {
digits: digits,
});
if (!currency || options.noSymbol) {
return formatted_value;
}
if (currency.position === "after") {
return formatted_value += '&nbsp;' + currency.symbol;
} else {
return currency.symbol + '&nbsp;' + formatted_value;
}
}
/**
* Returns a string representing the value of the selection.
*
* @param {string|false} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.escape=false] if true, escapes the formatted value
*/
function formatSelection(value, field, options) {
var val = _.find(field.selection, function (option) {
return option[0] === value;
});
if (!val) {
return '';
}
value = val[1];
if (options && options.escape) {
value = _.escape(value);
}
return value;
}
////////////////////////////////////////////////////////////////////////////////
// Parse
////////////////////////////////////////////////////////////////////////////////
/**
* Create an Date object
* The method toJSON return the formated value to send value server side
*
* @param {string} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.isUTC] the formatted date is utc
* @param {boolean} [options.timezone=false] format the date after apply the timezone
* offset
* @returns {Moment|false} Moment date object
*/
function parseDate(value, field, options) {
if (!value) {
return false;
}
var datePattern = time.getLangDateFormat();
var datePatternWoZero = datePattern.replace('MM','M').replace('DD','D');
var date;
if (options && options.isUTC) {
date = moment.utc(value);
} else {
date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601], true);
}
if (date.isValid()) {
if (date.year() === 0) {
date.year(moment.utc().year());
}
if (date.year() >= 1900) {
date.toJSON = function () {
return this.clone().locale('en').format('YYYY-MM-DD');
};
return date;
}
}
throw new Error(_.str.sprintf(core._t("'%s' is not a correct date"), value));
}
/**
* Create an Date object
* The method toJSON return the formated value to send value server side
*
* @param {string} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.isUTC] the formatted date is utc
* @param {boolean} [options.timezone=false] format the date after apply the timezone
* offset
* @returns {Moment|false} Moment date object
*/
function parseDateTime(value, field, options) {
if (!value) {
return false;
}
var datePattern = time.getLangDateFormat(),
timePattern = time.getLangTimeFormat();
var datePatternWoZero = datePattern.replace('MM','M').replace('DD','D'),
timePatternWoZero = timePattern.replace('HH','H').replace('mm','m').replace('ss','s');
var pattern1 = datePattern + ' ' + timePattern;
var pattern2 = datePatternWoZero + ' ' + timePatternWoZero;
var datetime;
if (options && options.isUTC) {
// phatomjs crash if we don't use this format
datetime = moment.utc(value.replace(' ', 'T') + 'Z');
} else {
datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601], true);
if (options && options.timezone) {
datetime.add(-session.getTZOffset(datetime), 'minutes');
}
}
if (datetime.isValid()) {
if (datetime.year() === 0) {
datetime.year(moment.utc().year());
}
if (datetime.year() >= 1900) {
datetime.toJSON = function () {
return this.clone().locale('en').format('YYYY-MM-DD HH:mm:ss');
};
return datetime;
}
}
throw new Error(_.str.sprintf(core._t("'%s' is not a correct datetime"), value));
}
/**
* Parse a String containing number in language formating
*
* @param {string} value
* The string to be parsed with the setting of thousands and
* decimal separator
* @returns {float|NaN} the number value contained in the string representation
*/
function parseNumber(value) {
if (core._t.database.parameters.thousands_sep) {
var escapedSep = _.str.escapeRegExp(core._t.database.parameters.thousands_sep);
value = value.replace(new RegExp(escapedSep, 'g'), '');
}
if (core._t.database.parameters.decimal_point) {
value = value.replace(core._t.database.parameters.decimal_point, '.');
}
return Number(value);
}
/**
* Parse a String containing float in language formating
*
* @param {string} value
* The string to be parsed with the setting of thousands and
* decimal separator
* @returns {float}
* @throws {Error} if no float is found respecting the language configuration
*/
function parseFloat(value) {
var parsed = parseNumber(value);
if (isNaN(parsed)) {
throw new Error(_.str.sprintf(core._t("'%s' is not a correct float"), value));
}
return parsed;
}
/**
* Parse a String containing currency symbol and returns amount
*
* @param {string} value
* The string to be parsed
* We assume that a monetary is always a pair (symbol, amount) separated
* by a non breaking space. A simple float can also be accepted as value
* @param {Object} [field]
* a description of the field (returned by fields_get for example).
* @param {Object} [options] additional options.
* @param {Object} [options.currency] - the description of the currency to use
* @param {integer} [options.currency_id]
* the id of the 'res.currency' to use (ignored if options.currency)
* @param {string} [options.currency_field]
* the name of the field whose value is the currency id
* (ignore if options.currency or options.currency_id)
* Note: if not given it will default to the field currency_field value
* or to 'currency_id'.
* @param {Object} [options.data]
* a mapping of field name to field value, required with
* options.currency_field
*
* @returns {float} the float value contained in the string representation
* @throws {Error} if no float is found or if parameter does not respect monetary condition
*/
function parseMonetary(value, field, options) {
var values = value.split('&nbsp;');
if (values.length === 1) {
return parseFloat(value);
}
else if (values.length !== 2) {
throw new Error(_.str.sprintf(core._t("'%s' is not a correct monetary field"), value));
}
options = options || {};
var currency = options.currency;
if (!currency) {
var currency_id = options.currency_id;
if (!currency_id && options.data) {
var currency_field = options.currency_field || field.currency_field || 'currency_id';
currency_id = options.data[currency_field] && options.data[currency_field].res_id;
}
currency = session.get_currency(currency_id);
}
return parseFloat(values[0] === currency.symbol ? values[1] : values[0]);
}
function parseFloatTime(value) {
var factor = 1;
if (value[0] === '-') {
value = value.slice(1);
factor = -1;
}
var float_time_pair = value.split(":");
if (float_time_pair.length !== 2)
return factor * parseFloat(value);
var hours = parseInteger(float_time_pair[0]);
var minutes = parseInteger(float_time_pair[1]);
return factor * (hours + (minutes / 60));
}
/**
* Parse a String containing integer with language formating
*
* @param {string} value
* The string to be parsed with the setting of thousands and
* decimal separator
* @returns {integer}
* @throws {Error} if no integer is found respecting the language configuration
*/
function parseInteger(value) {
var parsed = parseNumber(value);
// do not accept not numbers or float values
if (isNaN(parsed) || parsed % 1 || parsed < -2147483648 || parsed > 2147483647) {
throw new Error(_.str.sprintf(core._t("'%s' is not a correct integer"), value));
}
return parsed;
}
/**
* Creates an object with id and display_name.
*
* @param {Array|number|string|Object} value
* The given value can be :
* - an array with id as first element and display_name as second element
* - a number or a string representing the id (the display_name will be
* returned as undefined)
* - an object, simply returned untouched
* @returns {Object} (contains the id and display_name)
* Note: if the given value is not an array, a string or a
* number, the value is returned untouched.
*/
function parseMany2one(value) {
if (_.isArray(value)) {
return {
id: value[0],
display_name: value[1],
};
}
if (_.isNumber(value) || _.isString(value)) {
return {
id: parseInt(value, 10),
};
}
return value;
}
return {
format: {
binary: formatBinary,
boolean: formatBoolean,
char: formatChar,
date: formatDate,
datetime: formatDateTime,
float: formatFloat,
float_time: formatFloatTime,
html: _.identity, // todo
integer: formatInteger,
many2many: formatX2Many,
many2one: formatMany2one,
monetary: formatMonetary,
one2many: formatX2Many,
reference: formatMany2one,
selection: formatSelection,
text: formatChar,
},
parse: {
binary: _.identity,
boolean: _.identity, // todo
char: _.identity, // todo
date: parseDate, // todo
datetime: parseDateTime, // todo
float: parseFloat,
float_time: parseFloatTime,
html: _.identity, // todo
integer: parseInteger,
many2many: _.identity, // todo
many2one: parseMany2one,
monetary: parseMonetary,
one2many: _.identity,
reference: parseMany2one,
selection: _.identity, // todo
text: _.identity, // todo
},
};
});