[MIG] web_widget_float_formula: v9 with i18n

* Update JS to use v9 module system
* Fix non-functioning logic for obtaining the value of an input element
* Eliminate redundant calls (e.g. there were multiple calls to eval)
* Modify formula cleanup to use localized decimal point and thousands separator
 characters
* Add JS unit tests
This commit is contained in:
Oleg Bulkin
2016-09-12 11:33:29 -07:00
parent d79a4c20ba
commit d02ce7f647
10 changed files with 369 additions and 179 deletions

View File

@@ -1,132 +0,0 @@
/*******************************************************************************
See __openerp__.py file for Copyright and Licence Informations.
*******************************************************************************/
openerp.web_widget_float_formula = function (instance) {
instance.web.FormView = instance.web.FormView.extend({
/***********************************************************************
Overload section
***********************************************************************/
/**
* Overload : '_process_save' function
1: to force computation of formula if the user realize a keydown directly after the formula input in a tree view ;
2: to clean up the '_formula_text' value in all case to avoid bugs in tree view ;
*/
_process_save: function(save_obj) {
for (var f in this.fields) {
if (!this.fields.hasOwnProperty(f)) { continue; }
f = this.fields[f];
if (f.hasOwnProperty('_formula_text')){
currentval = f.$('input').attr('value')
if (typeof currentval != 'undefined'){
formula = f._get_valid_expression(currentval);
if (formula){
f._compute_result();
}
}
f._clean_formula_text();
}
}
return this._super(save_obj);
},
});
instance.web.form.FieldFloat = instance.web.form.FieldFloat.extend({
/***********************************************************************
Overload section
***********************************************************************/
/**
* Overload : 'start' function to catch 'blur' and 'focus' events.
*/
start: function() {
this.on("blurred", this, this._compute_result);
this.on("focused", this, this._display_formula);
return this._super();
},
/**
* Overload : 'initialize_content' function to clean '_formula_text' value.
*/
initialize_content: function() {
this._clean_formula_text();
return this._super();
},
/***********************************************************************
Custom section
***********************************************************************/
/**
* keep in memory the formula to allow user to edit it again.
The formula has to be keeped in memory until a 'save' action.
*/
_formula_text: '',
/**
* Clean '_formula_text' value.
*/
_clean_formula_text: function() {
this._formula_text = '';
},
/**
* Return a valid formula from a val, if possible.
Otherwise, return false.
*/
_get_valid_expression: function(val) {
// Trim the value
currenttxt = val.toString().replace(/^\s+|\s+$/g, '');
// Test if the value is a formula
if (currenttxt[0] == '=') {
// allowed chars : [0-9] .,+-/*() and spaces
myreg = RegExp('[0-9]|\\s|\\.|,|\\(|\\)|\\+|\\-|\\*|\\/','g')
// Test to avoid code injonction in eval function.
if (currenttxt.substring(1).replace(myreg, '') == ''){
try {
// Try to compute
formula = currenttxt.substring(1).replace(/,/g,'.');
var floatval = eval(formula);
}catch (e) {}
if (typeof (floatval) != 'undefined'){
return formula;
}
}
}
return false;
},
/**
* test if the content of the field is a valid formula,
* compute the result, and replace the current value by the final result.
*/
_compute_result: function() {
var formula
// Erase old formula
this._formula_text = '';
formula = this._get_valid_expression(this.$el.find('input').attr('value'));
if (formula){
// Store new formula
this._formula_text = "=" + formula;
// put the result in the field
this.set_value(eval(formula));
// Force rendering anyway to avoid format loss if no change
this.render_value();
}
},
/**
* Display the stored formula in the field, to allow modification.
*/
_display_formula: function() {
if (this._formula_text != ''){
this.$el.find('input').val(this._formula_text);
}
},
});
};

View File

@@ -0,0 +1,103 @@
/**
* Copyright GRAP
* Copyright 2016 LasLabs Inc.
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
**/
odoo.define('web_widget_float_formula', function(require) {
"use strict";
var form_view = require('web.FormView');
form_view.include({
// Ensure that formula is computed even if user saves right away and
// clean up '_formula_text' value to avoid bugs in tree view
_process_save: function(save_obj) {
for (var f in this.fields) {
if (!this.fields.hasOwnProperty(f)) { continue; }
f = this.fields[f];
if (f.hasOwnProperty('_formula_text')) {
f._compute_result();
f._clean_formula_text();
}
}
return this._super(save_obj);
},
});
var core = require('web.core');
core.bus.on('web_client_ready', null, function () {
// Import localization values used to eval formula
var translation_params = core._t.database.parameters;
var decimal_point = translation_params.decimal_point;
var thousands_sep = translation_params.thousands_sep;
var field_float = require('web.form_widgets').FieldFloat;
field_float.include({
start: function() {
this.on('blurred', this, this._compute_result);
this.on('focused', this, this._display_formula);
return this._super();
},
initialize_content: function() {
this._clean_formula_text();
return this._super();
},
_formula_text: '',
_clean_formula_text: function() {
this._formula_text = '';
},
_process_formula: function(formula) {
var clean_formula = formula.toString().replace(/^\s+|\s+$/g, '');
if (clean_formula[0] == '=') {
clean_formula = clean_formula.substring(1);
var myreg = RegExp('[0-9]|\\s|\\.|,|\\(|\\)|\\+|\\-|\\*|\\/','g');
if (clean_formula.replace(myreg, '') === '') {
return clean_formula;
}
}
return false;
},
_eval_formula: function(formula) {
var value;
formula = formula.replace(thousands_sep, '').replace(decimal_point, '.');
try {
value = eval(formula);
}
catch(e) {}
if (typeof value != 'undefined') {
return value;
}
return false;
},
_compute_result: function() {
this._clean_formula_text();
var formula = this._process_formula(this.$el.find('input').val());
if (formula !== false) {
var value = this._eval_formula(formula);
if (value !== false) {
this._formula_text = "=" + formula;
this.set_value(value);
// Force rendering to avoid format loss if there's no change
this.render_value();
}
}
},
// Display the formula stored in the field to allow modification
_display_formula: function() {
if (this._formula_text !== '') {
this.$el.find('input').val(this._formula_text);
}
},
});
});
});

View File

@@ -0,0 +1,161 @@
/**
* Copyright 2016 LasLabs Inc.
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
**/
odoo.define_section('web_widget_float_formula', ['web.form_common', 'web.form_widgets', 'web.core'], function(test) {
'use strict';
window.test_setup = function(self, form_common, form_widgets, core) {
core.bus.trigger('web_client_ready');
var field_manager = new form_common.DefaultFieldManager(null, {});
var filler = {'attrs': {}}; // Needed to instantiate FieldFloat
self.field = new form_widgets.FieldFloat(field_manager, filler);
self.$element = $('<input>');
self.field.$el.append(self.$element);
};
test('Float fields should have a _formula_text property that defaults to an empty string',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.strictEqual(this.field._formula_text, '');
});
test('.initialize_content() on float fields should clear the _formula_text property',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = 'test';
this.field.initialize_content();
assert.strictEqual(this.field._formula_text, '');
});
test('._clean_formula_text() on float fields should clear the _formula_text property',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = 'test';
this.field._clean_formula_text();
assert.strictEqual(this.field._formula_text, '');
});
test('._process_formula() on float fields should return false when given invalid formulas',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.strictEqual(this.field._process_formula('2*3'), false);
assert.strictEqual(this.field._process_formula('=2*3a'), false);
});
test('._process_formula() on float fields should properly process a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.strictEqual(this.field._process_formula(' =2*3\n'), '2*3');
});
test('._eval_formula() on float fields should properly evaluate a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula('2*3'), 6);
});
test('._eval_formula() on float fields should properly handle alternative decimal points and thousands seps',
function(assert, form_common, form_widgets, core) {
var translation_params = core._t.database.parameters;
translation_params.decimal_point = ',';
translation_params.thousands_sep = '.';
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula('2.000*3,5'), 7000);
});
test('._eval_formula() on float fields should return false when given an input that evals to undefined',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula(''), false);
});
test('._eval_formula() on float fields should return false when given an input that cannot be evaluated',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
assert.equal(this.field._eval_formula('*/'), false);
});
test('._compute_result() on float fields should always clean up _formula_text',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = 'test';
this.field._compute_result();
assert.strictEqual(this.field._formula_text, '');
});
test('._compute_result() should not change the value of the associated input when it is not a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.$element.val('=2*3a');
this.field._compute_result();
assert.strictEqual(this.$element.val(), '=2*3a');
});
test('._compute_result() should not change the value of the associated input when it cannot be evaled',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.$element.val('=*/');
this.field._compute_result();
assert.strictEqual(this.$element.val(), '=*/');
});
test('._compute_result() should behave properly when the current value of the input element is a valid formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.$element.val('=2*3');
this.field._compute_result();
assert.equal(this.$element.val(), '6');
assert.strictEqual(this.field._formula_text, '=2*3');
});
test('._display_formula() should update the value of the input element when there is a stored formula',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field._formula_text = "test";
this.field._display_formula();
assert.equal(this.$element.val(), 'test');
});
test('.start() on float fields should add a handler that calls ._compute_result() when the field is blurred',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field.called = false;
this.field._compute_result = function() {
this.called = true;
};
this.field.start();
this.field.trigger('blurred');
assert.strictEqual(this.field.called, true);
});
test('.start() on float fields should add a handler that calls ._display_formula() when the field is focused',
function(assert, form_common, form_widgets, core) {
window.test_setup(this, form_common, form_widgets, core);
this.field.called = false;
this.field._display_formula = function() {
this.called = true;
};
this.field.start();
this.field.trigger('focused');
assert.strictEqual(this.field.called, true);
});
});