diff options
Diffstat (limited to 'addons/web/static/src/js/framework')
24 files changed, 0 insertions, 6861 deletions
diff --git a/addons/web/static/src/js/framework/ajax.js b/addons/web/static/src/js/framework/ajax.js deleted file mode 100644 index dee6f4599b4..00000000000 --- a/addons/web/static/src/js/framework/ajax.js +++ /dev/null @@ -1,399 +0,0 @@ -odoo.define('web.ajax', function (require) { -"use strict"; - -var core = require('web.core'); -var utils = require('web.utils'); -var time = require('web.time'); - -function genericJsonRpc (fct_name, params, fct) { - var data = { - jsonrpc: "2.0", - method: fct_name, - params: params, - id: Math.floor(Math.random() * 1000 * 1000 * 1000) - }; - var xhr = fct(data); - var result = xhr.pipe(function(result) { - core.bus.trigger('rpc:result', data, result); - if (result.error !== undefined) { - if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") { - console.error("Server application error", JSON.stringify(result.error)); - } - return $.Deferred().reject("server", result.error); - } else { - return result.result; - } - }, function() { - //console.error("JsonRPC communication error", _.toArray(arguments)); - var def = $.Deferred(); - return def.reject.apply(def, ["communication"].concat(_.toArray(arguments))); - }); - // FIXME: jsonp? - result.abort = function () { if (xhr.abort) xhr.abort(); }; - return result; -} - -function jsonRpc(url, fct_name, params, settings) { - return genericJsonRpc(fct_name, params, function(data) { - return $.ajax(url, _.extend({}, settings, { - url: url, - dataType: 'json', - type: 'POST', - data: JSON.stringify(data, time.date_to_utc), - contentType: 'application/json' - })); - }); -} - -function jsonpRpc(url, fct_name, params, settings) { - settings = settings || {}; - return genericJsonRpc(fct_name, params, function(data) { - var payload_str = JSON.stringify(data, time.date_to_utc); - var payload_url = $.param({r:payload_str}); - var force2step = settings.force2step || false; - delete settings.force2step; - var session_id = settings.session_id || null; - delete settings.session_id; - if (payload_url.length < 2000 && ! force2step) { - return $.ajax(url, _.extend({}, settings, { - url: url, - dataType: 'jsonp', - jsonp: 'jsonp', - type: 'GET', - cache: false, - data: {r: payload_str, session_id: session_id} - })); - } else { - var args = {session_id: session_id, id: data.id}; - var ifid = _.uniqueId('oe_rpc_iframe'); - var html = "<iframe src='javascript:false;' name='" + ifid + "' id='" + ifid + "' style='display:none'></iframe>"; - var $iframe = $(html); - var nurl = 'jsonp=1&' + $.param(args); - nurl = url.indexOf("?") !== -1 ? url + "&" + nurl : url + "?" + nurl; - var $form = $('<form>') - .attr('method', 'POST') - .attr('target', ifid) - .attr('enctype', "multipart/form-data") - .attr('action', nurl) - .append($('<input type="hidden" name="r" />').attr('value', payload_str)) - .hide() - .appendTo($('body')); - var cleanUp = function() { - if ($iframe) { - $iframe.unbind("load").remove(); - } - $form.remove(); - }; - var deferred = $.Deferred(); - // the first bind is fired up when the iframe is added to the DOM - $iframe.bind('load', function() { - // the second bind is fired up when the result of the form submission is received - $iframe.unbind('load').bind('load', function() { - $.ajax({ - url: url, - dataType: 'jsonp', - jsonp: 'jsonp', - type: 'GET', - cache: false, - data: {session_id: session_id, id: data.id} - }).always(function() { - cleanUp(); - }).done(function() { - deferred.resolve.apply(deferred, arguments); - }).fail(function() { - deferred.reject.apply(deferred, arguments); - }); - }); - // now that the iframe can receive data, we fill and submit the form - $form.submit(); - }); - // append the iframe to the DOM (will trigger the first load) - $form.after($iframe); - if (settings.timeout) { - realSetTimeout(function() { - deferred.reject({}); - }, settings.timeout); - } - return deferred; - } - }); -} - -// helper function to make a rpc with a function name hardcoded to 'call' -function rpc(url, params, settings) { - return jsonRpc(url, 'call', params, settings); -} - -// helper -function realSetTimeout (fct, millis) { - var finished = new Date().getTime() + millis; - var wait = function() { - var current = new Date().getTime(); - if (current < finished) { - setTimeout(wait, finished - current); - } else { - fct(); - } - }; - setTimeout(wait, millis); -} - -function loadCSS(url) { - if (!$('link[href="' + url + '"]').length) { - $('head').append($('<link>', { - 'href': url, - 'rel': 'stylesheet', - 'type': 'text/css' - })); - } -} - -var loadJS = (function () { - var urls = []; - var defs = []; - - var load = function loadJS(url) { - // Check the DOM to see if a script with the specified url is already there - var alreadyRequired = ($('script[src="' + url + '"]').length > 0); - - // If loadJS was already called with the same URL, it will have a registered deferred indicating if - // the script has been fully loaded. If not, the deferred has to be initialized. This is initialized - // as already resolved if the script was already there without the need of loadJS. - var index = _.indexOf(urls, url); - if (index < 0) { - urls.push(url); - index = defs.push(alreadyRequired ? $.when() : $.Deferred()) - 1; - } - - // Get the script associated deferred and returns it after initializing the script if needed. The - // deferred is marked to be resolved on script load and rejected on script error. - var def = defs[index]; - if (!alreadyRequired) { - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = url; - script.onload = script.onreadystatechange = function() { - if ((script.readyState && script.readyState !== "loaded" && script.readyState !== "complete") || script.onload_done) { - return; - } - script.onload_done = true; - def.resolve(url); - }; - script.onerror = function () { - console.error("Error loading file", script.src); - def.reject(url); - }; - var head = document.head || document.getElementsByTagName('head')[0]; - head.appendChild(script); - } - return def; - }; - - return load; -})(); - - -/** - * Cooperative file download implementation, for ajaxy APIs. - * - * Requires that the server side implements an httprequest correctly - * setting the `fileToken` cookie to the value provided as the `token` - * parameter. The cookie *must* be set on the `/` path and *must not* be - * `httpOnly`. - * - * It would probably also be a good idea for the response to use a - * `Content-Disposition: attachment` header, especially if the MIME is a - * "known" type (e.g. text/plain, or for some browsers application/json - * - * @param {Object} options - * @param {String} [options.url] used to dynamically create a form - * @param {Object} [options.data] data to add to the form submission. If can be used without a form, in which case a form is created from scratch. Otherwise, added to form data - * @param {HTMLFormElement} [options.form] the form to submit in order to fetch the file - * @param {Function} [options.success] callback in case of download success - * @param {Function} [options.error] callback in case of request error, provided with the error body - * @param {Function} [options.complete] called after both ``success`` and ``error` callbacks have executed - */ -function get_file(options) { - // need to detect when the file is done downloading (not used - // yet, but we'll need it to fix the UI e.g. with a throbber - // while dump is being generated), iframe load event only fires - // when the iframe content loads, so we need to go smarter: - // http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx - var timer, token = new Date().getTime(), - cookie_name = 'fileToken', cookie_length = cookie_name.length, - CHECK_INTERVAL = 1000, id = _.uniqueId('get_file_frame'), - remove_form = false; - - - // iOS devices doesn't allow iframe use the way we do it, - // opening a new window seems the best way to workaround - if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) { - var params = _.extend({}, options.data || {}, {token: token}); - var url = options.session.url(options.url, params); - if (options.complete) { options.complete(); } - - return window.open(url); - } - - var $form, $form_data = $('<div>'); - - var complete = function () { - if (options.complete) { options.complete(); } - clearTimeout(timer); - $form_data.remove(); - $target.remove(); - if (remove_form && $form) { $form.remove(); } - }; - var $target = $('<iframe style="display: none;">') - .attr({id: id, name: id}) - .appendTo(document.body) - .load(function () { - try { - if (options.error) { - var body = this.contentDocument.body; - var nodes = body.children.length === 0 ? body.childNodes : body.children; - var node = nodes[1] || nodes[0]; - options.error(JSON.parse(node.textContent)); - } - } finally { - complete(); - } - }); - - if (options.form) { - $form = $(options.form); - } else { - remove_form = true; - $form = $('<form>', { - action: options.url, - method: 'POST' - }).appendTo(document.body); - } - if (core.csrf_token) { - $('<input type="hidden" name="csrf_token">') - .val(core.csrf_token) - .appendTo($form_data); - } - - var hparams = _.extend({}, options.data || {}, {token: token}); - _.each(hparams, function (value, key) { - var $input = $form.find('[name=' + key +']'); - if (!$input.length) { - $input = $('<input type="hidden" name="' + key + '">') - .appendTo($form_data); - } - $input.val(value); - }); - - $form - .append($form_data) - .attr('target', id) - .get(0).submit(); - - var waitLoop = function () { - var cookies = document.cookie.split(';'); - // setup next check - timer = setTimeout(waitLoop, CHECK_INTERVAL); - for (var i=0; i<cookies.length; ++i) { - var cookie = cookies[i].replace(/^\s*/, ''); - if (!cookie.indexOf(cookie_name === 0)) { continue; } - var cookie_val = cookie.substring(cookie_length + 1); - if (parseInt(cookie_val, 10) !== token) { continue; } - - // clear cookie - document.cookie = _.str.sprintf("%s=;expires=%s;path=/", - cookie_name, new Date().toGMTString()); - if (options.success) { options.success(); } - complete(); - return; - } - }; - timer = setTimeout(waitLoop, CHECK_INTERVAL); -}; - -function post (controller_url, data) { - - var progressHandler = function (deferred) { - return function (state) { - if(state.lengthComputable) { - deferred.notify({ - h_loaded: utils.human_size(state.loaded), - h_total : utils.human_size(state.total), - loaded : state.loaded, - total : state.total, - pcent : Math.round((state.loaded/state.total)*100) - }); - } - }; - }; - - var Def = $.Deferred(); - var postData = new FormData(); - - $.each(data, function(i,val) { - postData.append(i, val); - }); - if (core.csrf_token) { - postData.append('csrf_token', core.csrf_token); - } - - var xhr = new XMLHttpRequest(); - if(xhr.upload) xhr.upload.addEventListener('progress', progressHandler(Def), false); - - var ajaxDef = $.ajax(controller_url, { - xhr: function() {return xhr;}, - data: postData, - processData: false, - contentType: false, - type: 'POST' - }).then(function (data) {Def.resolve(data);}) - .fail(function (data) {Def.reject(data);}); - - return Def; -} - -var loadXML = (function () { - var loading = false; - var urls = []; - var qwebs = []; - var templates_def = $.Deferred(); - - var load = function loadXML(url, qweb) { - if (url) { - urls.push(url); - qwebs.push(qweb); - } - - if (!loading && urls.length) { - if (templates_def.state() === "resolved") { - templates_def = $.Deferred(); - } - - loading = true; - qwebs.shift().add_template(urls.shift(), function () { - loading = false; - if (!urls.length) { - templates_def.resolve(); - } - load(null); - }); - } - - return templates_def; - }; - - return load; -})(); - -return { - jsonRpc: jsonRpc, - jsonpRpc: jsonpRpc, - rpc: rpc, - loadCSS: loadCSS, - loadJS: loadJS, - loadXML: loadXML, - get_file: get_file, - post: post, -}; - -}); diff --git a/addons/web/static/src/js/framework/bus.js b/addons/web/static/src/js/framework/bus.js deleted file mode 100644 index bf33c1fe01e..00000000000 --- a/addons/web/static/src/js/framework/bus.js +++ /dev/null @@ -1,16 +0,0 @@ -odoo.define('web.Bus', function (require) { -"use strict"; - -var Class = require('web.Class'); -var mixins = require('web.mixins'); - -/** - * Event Bus used to bind events scoped in the current instance - */ -return Class.extend(mixins.EventDispatcherMixin, { - init: function() { - mixins.EventDispatcherMixin.init.call(this, parent); - }, -}); - -}); diff --git a/addons/web/static/src/js/framework/class.js b/addons/web/static/src/js/framework/class.js deleted file mode 100644 index 8e53f0ba06d..00000000000 --- a/addons/web/static/src/js/framework/class.js +++ /dev/null @@ -1,154 +0,0 @@ -odoo.define('web.Class', function () { -"use strict"; -/** - * Improved John Resig's inheritance, based on: - * - * Simple JavaScript Inheritance - * By John Resig http://ejohn.org/ - * MIT Licensed. - * - * Adds "include()" - * - * Defines The Class object. That object can be used to define and inherit classes using - * the extend() method. - * - * Example: - * - * var Person = Class.extend({ - * init: function(isDancing){ - * this.dancing = isDancing; - * }, - * dance: function(){ - * return this.dancing; - * } - * }); - * - * The init() method act as a constructor. This class can be instancied this way: - * - * var person = new Person(true); - * person.dance(); - * - * The Person class can also be extended again: - * - * var Ninja = Person.extend({ - * init: function(){ - * this._super( false ); - * }, - * dance: function(){ - * // Call the inherited version of dance() - * return this._super(); - * }, - * swingSword: function(){ - * return true; - * } - * }); - * - * When extending a class, each re-defined method can use this._super() to call the previous - * implementation of that method. - */ -var initializing = false; -var fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/; - -// The web Class implementation (does nothing) -function OdooClass(){}; - -/** - * Subclass an existing class - * - * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class - */ -OdooClass.extend = function() { - var _super = this.prototype; - // Support mixins arguments - var args = _.toArray(arguments); - args.unshift({}); - var prop = _.extend.apply(_,args); - - // Instantiate a web class (but only create the instance, - // don't run the init constructor) - initializing = true; - var This = this; - var prototype = new This(); - initializing = false; - - // Copy the properties over onto the new prototype - _.each(prop, function(val, name) { - // Check if we're overwriting an existing function - prototype[name] = typeof prop[name] == "function" && - fnTest.test(prop[name]) ? - (function(name, fn) { - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same - // method but on the super-class - this._super = _super[name]; - - // The method only need to be bound temporarily, so - // we remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]) : - prop[name]; - }); - - // The dummy class constructor - function Class() { - if(this.constructor !== OdooClass){ - throw new Error("You can only instanciate objects with the 'new' operator"); - } - // All construction is actually done in the init method - this._super = null; - if (!initializing && this.init) { - var ret = this.init.apply(this, arguments); - if (ret) { return ret; } - } - return this; - } - Class.include = function (properties) { - _.each(properties, function(val, name) { - if (typeof properties[name] !== 'function' - || !fnTest.test(properties[name])) { - prototype[name] = properties[name]; - } else if (typeof prototype[name] === 'function' - && prototype.hasOwnProperty(name)) { - prototype[name] = (function (name, fn, previous) { - return function () { - var tmp = this._super; - this._super = previous; - var ret = fn.apply(this, arguments); - this._super = tmp; - return ret; - }; - })(name, properties[name], prototype[name]); - } else if (typeof _super[name] === 'function') { - prototype[name] = (function (name, fn) { - return function () { - var tmp = this._super; - this._super = _super[name]; - var ret = fn.apply(this, arguments); - this._super = tmp; - return ret; - }; - })(name, properties[name]); - } - }); - }; - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.constructor = Class; - - // And make this class extendable - Class.extend = this.extend; - - return Class; -}; - -return OdooClass; -}); diff --git a/addons/web/static/src/js/framework/crash_manager.js b/addons/web/static/src/js/framework/crash_manager.js deleted file mode 100644 index af982ecd15d..00000000000 --- a/addons/web/static/src/js/framework/crash_manager.js +++ /dev/null @@ -1,211 +0,0 @@ -odoo.define('web.CrashManager', function (require) { -"use strict"; - -var ajax = require('web.ajax'); -var core = require('web.core'); -var Dialog = require('web.Dialog'); - -var QWeb = core.qweb; -var _t = core._t; -var _lt = core._lt; - -var map_title ={ - user_error: _lt('Warning'), - warning: _lt('Warning'), - access_error: _lt('Access Error'), - missing_error: _lt('Missing Record'), - validation_error: _lt('Validation Error'), - except_orm: _lt('Global Business Error'), - access_denied: _lt('Access Denied'), -}; - -var CrashManager = core.Class.extend({ - init: function() { - this.active = true; - }, - enable: function () { - this.active = true; - }, - disable: function () { - this.active = false; - }, - rpc_error: function(error) { - var self = this; - if (!this.active) { - return; - } - if (this.connection_lost) { - return; - } - if (error.code === -32098) { - core.bus.trigger('connection_lost'); - this.connection_lost = true; - var timeinterval = setInterval(function() { - ajax.jsonRpc('/web/webclient/version_info').then(function() { - clearInterval(timeinterval); - core.bus.trigger('connection_restored'); - self.connection_lost = false; - }); - }, 2000); - return; - } - var handler = core.crash_registry.get(error.data.name, true); - if (handler) { - new (handler)(this, error).display(); - return; - } - if (error.data.name === "openerp.http.SessionExpiredException" || error.data.name === "werkzeug.exceptions.Forbidden") { - this.show_warning({type: _t("Odoo Session Expired"), data: {message: _t("Your Odoo session expired. Please refresh the current web page.")}}); - return; - } - if (_.has(map_title, error.data.exception_type)) { - if(error.data.exception_type === 'except_orm'){ - if(error.data.arguments[1]) { - error = _.extend({}, error, - { - data: _.extend({}, error.data, - { - message: error.data.arguments[1], - title: error.data.arguments[0] !== 'Warning' ? (" - " + error.data.arguments[0]) : '', - }) - }); - } - else { - error = _.extend({}, error, - { - data: _.extend({}, error.data, - { - message: error.data.arguments[0], - title: '', - }) - }); - } - } - else { - error = _.extend({}, error, - { - data: _.extend({}, error.data, - { - message: error.data.arguments[0], - title: map_title[error.data.exception_type] !== 'Warning' ? (" - " + map_title[error.data.exception_type]) : '', - }) - }); - } - - this.show_warning(error); - //InternalError - - } else { - this.show_error(error); - } - }, - show_warning: function(error) { - if (!this.active) { - return; - } - new Dialog(this, { - size: 'medium', - title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"), - subtitle: error.data.title, - $content: $(QWeb.render('CrashManager.warning', {error: error})) - }).open(); - }, - show_error: function(error) { - if (!this.active) { - return; - } - var dialog = new Dialog(this, { - title: _.str.capitalize(error.type || error.message) || _t("Odoo Error"), - $content: $(QWeb.render('CrashManager.error', {error: error})) - }).open(); - - // When the dialog opens, initialize the copy feature and destroy it when the dialog is closed - var $clipboardBtn; - var clipboard; - dialog.opened(function () { - $clipboardBtn = dialog.$(".o_clipboard_button"); - $clipboardBtn.tooltip({title: _t("Copied !"), trigger: "manual", placement: "left"}); - clipboard = new window.Clipboard($clipboardBtn[0], { - text: function () { - return (_t("Error") + ":\n" + error.message + "\n\n" + error.data.debug).trim(); - } - }); - clipboard.on("success", function (e) { - _.defer(function () { - $clipboardBtn.tooltip("show"); - _.delay(function () { - $clipboardBtn.tooltip("hide"); - }, 800); - }); - }); - }); - dialog.on("closed", this, function () { - $clipboardBtn.tooltip("destroy"); - clipboard.destroy(); - }); - - // When the full traceback is shown, scroll it to the end (useful for better python error reporting) - dialog.$(".o_error_detail").on("shown.bs.collapse", function (e) { - e.target.scrollTop = e.target.scrollHeight; - }); - }, - show_message: function(exception) { - this.show_error({ - type: _t("Odoo Client Error"), - message: exception, - data: {debug: ""} - }); - }, -}); - -/** - An interface to implement to handle exceptions. Register implementation in instance.web.crash_manager_registry. -*/ -var ExceptionHandler = { - /** - @param parent The parent. - @param error The error object as returned by the JSON-RPC implementation. - */ - init: function(parent, error) {}, - /** - Called to inform to display the widget, if necessary. A typical way would be to implement - this interface in a class extending instance.web.Dialog and simply display the dialog in this - method. - */ - display: function() {}, -}; - - -/** - * Handle redirection warnings, which behave more or less like a regular - * warning, with an additional redirection button. - */ -var RedirectWarningHandler = Dialog.extend(ExceptionHandler, { - init: function(parent, error) { - this._super(parent); - this.error = error; - }, - display: function() { - var self = this; - var error = this.error; - error.data.message = error.data.arguments[0]; - - new Dialog(this, { - size: 'medium', - title: _.str.capitalize(error.type) || _t("Odoo Warning"), - buttons: [ - {text: error.data.arguments[2], classes : "btn-primary", click: function() { - window.location.href = '#action='+error.data.arguments[1]; - self.destroy(); - }}, - {text: _t("Cancel"), click: function() { self.destroy(); }, close: true} - ], - $content: QWeb.render('CrashManager.warning', {error: error}), - }).open(); - } -}); - -core.crash_registry.add('openerp.exceptions.RedirectWarning', RedirectWarningHandler); - -return CrashManager; -}); diff --git a/addons/web/static/src/js/framework/data.js b/addons/web/static/src/js/framework/data.js deleted file mode 100644 index 1d4258019ae..00000000000 --- a/addons/web/static/src/js/framework/data.js +++ /dev/null @@ -1,1135 +0,0 @@ -odoo.define('web.data', function (require) { -"use strict"; - -var Class = require('web.Class'); -var mixins = require('web.mixins'); -var Model = require('web.Model'); -var session = require('web.session'); -var translation = require('web.translation'); -var pyeval = require('web.pyeval'); -var utils = require('web.utils'); - -var _t = translation._t; - -/** - * Serializes the sort criterion array of a dataset into a form which can be - * consumed by OpenERP's RPC APIs. - * - * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting - * @returns {String} SQL-like sorting string (``ORDER BY``) clause - */ -function serialize_sort(criterion) { - return _.map(criterion, - function (criteria) { - if (criteria[0] === '-') { - return criteria.slice(1) + ' DESC'; - } - return criteria + ' ASC'; - }).join(', '); -} - -/** - * Reverse of the serialize_sort function: convert an array of SQL-like sort - * descriptors into a list of fields prefixed with '-' if necessary. - */ -function deserialize_sort(criterion) { - return _.map(criterion, function (criteria) { - var split = _.without(criteria.split(' '), ''); - return (split[1] && split[1].toLowerCase() === 'desc' ? '-' : '') + split[0]; - }); -} - -var Query = Class.extend({ - init: function (model, fields) { - this._model = model; - this._fields = fields; - this._filter = []; - this._context = {}; - this._lazy = true; - this._limit = false; - this._offset = 0; - this._order_by = []; - }, - clone: function (to_set) { - to_set = to_set || {}; - var q = new Query(this._model, this._fields); - q._context = this._context; - q._filter = this._filter; - q._lazy = this._lazy; - q._limit = this._limit; - q._offset = this._offset; - q._order_by = this._order_by; - - for(var key in to_set) { - if (!to_set.hasOwnProperty(key)) { continue; } - switch(key) { - case 'filter': - q._filter = new CompoundDomain( - q._filter, to_set.filter); - break; - case 'context': - q._context = new CompoundContext( - q._context, to_set.context); - break; - case 'lazy': - case 'limit': - case 'offset': - case 'order_by': - q['_' + key] = to_set[key]; - } - } - return q; - }, - _execute: function (options) { - var self = this; - options = options || {}; - return session.rpc('/web/dataset/search_read', { - model: this._model.name, - fields: this._fields || false, - domain: pyeval.eval('domains', - [this._model.domain(this._filter)]), - context: pyeval.eval('contexts', - [this._model.context(this._context)]), - offset: this._offset, - limit: this._limit, - sort: serialize_sort(this._order_by) - }, options).then(function (results) { - self._count = results.length; - return results.records; - }, null); - }, - /** - * Fetches the first record matching the query, or null - * - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<Object|null>} - */ - first: function (options) { - var self = this; - return this.clone({limit: 1})._execute(options).then(function (records) { - delete self._count; - if (records.length) { return records[0]; } - return null; - }); - }, - /** - * Fetches all records matching the query - * - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<Array<>>} - */ - all: function (options) { - return this._execute(options); - }, - /** - * Fetches the number of records matching the query in the database - * - * @returns {jQuery.Deferred<Number>} - */ - count: function () { - if (this._count !== undefined) { return $.when(this._count); } - return this._model.call( - 'search_count', [this._filter], { - context: this._model.context(this._context)}); - }, - /** - * Performs a groups read according to the provided grouping criterion - * - * @param {String|Array<String>} grouping - * @returns {jQuery.Deferred<Array<openerp.web.QueryGroup>> | null} - */ - group_by: function (grouping) { - var ctx = pyeval.eval( - 'context', this._model.context(this._context)); - - // undefined passed in explicitly (!) - if (_.isUndefined(grouping)) { - grouping = []; - } - - if (!(grouping instanceof Array)) { - grouping = _.toArray(arguments); - } - if (_.isEmpty(grouping) && !ctx.group_by_no_leaf) { - return null; - } - var raw_fields = _.map(grouping.concat(this._fields || []), function (field) { - return field.split(':')[0]; - }); - - var self = this; - return this._model.call('read_group', { - groupby: grouping, - fields: _.uniq(raw_fields), - domain: this._model.domain(this._filter), - context: ctx, - offset: this._offset, - lazy: this._lazy, - limit: this._limit, - orderby: serialize_sort(this._order_by) || false - }).then(function (results) { - return _(results).map(function (result) { - // FIX: querygroup initialization - result.__context = result.__context || {}; - result.__context.group_by = result.__context.group_by || []; - _.defaults(result.__context, ctx); - var grouping_fields = self._lazy ? [grouping[0]] : grouping; - return new QueryGroup( - self._model.name, grouping_fields, result); - }); - }); - }, - /** - * Creates a new query with the union of the current query's context and - * the new context. - * - * @param context context data to add to the query - * @returns {openerp.web.Query} - */ - context: function (context) { - if (!context) { return this; } - return this.clone({context: context}); - }, - /** - * Creates a new query with the union of the current query's filter and - * the new domain. - * - * @param domain domain data to AND with the current query filter - * @returns {openerp.web.Query} - */ - filter: function (domain) { - if (!domain) { return this; } - return this.clone({filter: domain}); - }, - /** - * Creates a new query with the provided parameter lazy replacing the current - * query's own. - * - * @param {Boolean} lazy indicates if the read_group should return only the - * first level of groupby records, or should return the records grouped by - * all levels at once (so, it makes only 1 db request). - * @returns {openerp.web.Query} - */ - lazy: function (lazy) { - return this.clone({lazy: lazy}); - }, - /** - * Creates a new query with the provided limit replacing the current - * query's own limit - * - * @param {Number} limit maximum number of records the query should retrieve - * @returns {openerp.web.Query} - */ - limit: function (limit) { - return this.clone({limit: limit}); - }, - /** - * Creates a new query with the provided offset replacing the current - * query's own offset - * - * @param {Number} offset number of records the query should skip before starting its retrieval - * @returns {openerp.web.Query} - */ - offset: function (offset) { - return this.clone({offset: offset}); - }, - /** - * Creates a new query with the provided ordering parameters replacing - * those of the current query - * - * @param {String...} fields ordering clauses - * @returns {openerp.web.Query} - */ - order_by: function (fields) { - if (fields === undefined) { return this; } - if (!(fields instanceof Array)) { - fields = _.toArray(arguments); - } - if (_.isEmpty(fields)) { return this; } - return this.clone({order_by: fields}); - } -}); - -var QueryGroup = Class.extend({ - init: function (model, grouping_fields, read_group_group) { - // In cases where group_by_no_leaf and no group_by, the result of - // read_group has aggregate fields but no __context or __domain. - // Create default (empty) values for those so that things don't break - var fixed_group = _.extend( - {__context: {group_by: []}, __domain: []}, - read_group_group); - - var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count'; - var aggregates = {}; - _(fixed_group).each(function (value, key) { - if (key.indexOf('__') === 0 - || _.contains(grouping_fields, key) - || (key === count_key)) { - return; - } - aggregates[key] = value || 0; - }); - - this.model = new Model( - model, fixed_group.__context, fixed_group.__domain); - - var group_size = fixed_group[count_key] || fixed_group.__count || 0; - var leaf_group = fixed_group.__context.group_by.length === 0; - - var value = (grouping_fields.length === 1) - ? fixed_group[grouping_fields[0]] - : _.map(grouping_fields, function (field) { return fixed_group[field]; }); - var grouped_on = (grouping_fields.length === 1) - ? grouping_fields[0] - : grouping_fields; - this.attributes = { - folded: !!(fixed_group.__fold), - grouped_on: grouped_on, - // if terminal group (or no group) and group_by_no_leaf => use group.__count - length: group_size, - value: value, - // A group is open-able if it's not a leaf in group_by_no_leaf mode - has_children: !(leaf_group && fixed_group.__context.group_by_no_leaf), - - aggregates: aggregates - }; - }, - get: function (key) { - return this.attributes[key]; - }, - subgroups: function () { - return this.model.query().group_by(this.model.context().group_by); - }, - query: function () { - return this.model.query.apply(this.model, arguments); - } -}); - -var DataSet = Class.extend(mixins.PropertiesMixin, { - /** - * Collection of OpenERP records, used to share records and the current selection between views. - * - * @constructs instance.web.DataSet - * - * @param {String} model the OpenERP model this dataset will manage - */ - init: function(parent, model, context) { - mixins.PropertiesMixin.init.call(this); - this.model = model; - this.context = context || {}; - this.index = null; - this._sort = []; - this._model = new Model(model, context); - this.orderer = new utils.DropMisordered(); - }, - previous: function () { - this.index -= 1; - if (!this.ids.length) { - this.index = null; - } else if (this.index < 0) { - this.index = this.ids.length - 1; - } - return this; - }, - next: function () { - this.index += 1; - if (!this.ids.length) { - this.index = null; - } else if (this.index >= this.ids.length) { - this.index = 0; - } - return this; - }, - select_id: function(id) { - var idx = this.get_id_index(id); - if (idx === null) { - return false; - } else { - this.index = idx; - return true; - } - }, - get_id_index: function(id) { - for (var i=0, ii=this.ids.length; i<ii; i++) { - // Here we use type coercion because of the mess potentially caused by - // OpenERP ids fetched from the DOM as string. (eg: dhtmlxcalendar) - // OpenERP ids can be non-numeric too ! (eg: recursive events in calendar) - if (id == this.ids[i]) { - return i; - } - } - return null; - }, - /** - * Read records. - * - * @param {Array} ids identifiers of the records to read - * @param {Array} [fields] fields to read and return, by default all fields are returned - * @param {Object} [options] - * @returns {$.Deferred} - */ - read_ids: function (ids, fields, options) { - if (_.isEmpty(ids)) - return $.Deferred().resolve([]); - - options = options || {}; - var method = 'read'; - var ids_arg = ids; - var context = this.get_context(options.context); - if (options.check_access_rule === true){ - method = 'search_read'; - ids_arg = [['id', 'in', ids]]; - context = new CompoundContext(context, {active_test: false}); - } - return this._model.call(method, - [ids_arg, fields || false], - {context: context}) - .then(function (records) { - if (records.length <= 1) { return records; } - var indexes = {}; - for (var i = 0; i < ids.length; i++) { - indexes[ids[i]] = i; - } - records.sort(function (a, b) { - return indexes[a.id] - indexes[b.id]; - }); - return records; - }); - }, - /** - * Read a slice of the records represented by this DataSet, based on its - * domain and context. - * - * @param {Array} [fields] fields to read and return, by default all fields are returned - * @params {Object} [options] - * @param {Number} [options.offset=0] The index from which selected records should be returned - * @param {Number} [options.limit=null] The maximum number of records to return - * @returns {$.Deferred} - */ - read_slice: function (fields, options) { - var self = this; - options = options || {}; - var query = this._model.query(fields) - .limit(options.limit || false) - .offset(options.offset || 0) - .all(); - return this.orderer.add(query).done(function (records) { - self.ids = _(records).pluck('id'); - }); - }, - /** - * Reads the current dataset record (from its index) - * - * @params {Array} [fields] fields to read and return, by default all fields are returned - * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @returns {$.Deferred} - */ - read_index: function (fields, options) { - options = options || {}; - return this.read_ids([this.ids[this.index]], fields, options).then(function (records) { - if (_.isEmpty(records)) { return $.Deferred().reject().promise(); } - return records[0]; - }); - }, - /** - * Reads default values for the current model - * - * @param {Array} [fields] fields to get default values for, by default all defaults are read - * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @returns {$.Deferred} - */ - default_get: function(fields, options) { - options = options || {}; - return this._model.call('default_get', - [fields], {context: this.get_context(options.context)}); - }, - /** - * Creates a new record in db - * - * @param {Object} data field values to set on the new record - * @param {Object} options Dictionary that can contain the following keys: - * - readonly_fields: Values from readonly fields that were updated by - * on_changes. Only used by the BufferedDataSet to make the o2m work correctly. - * @returns {$.Deferred} - */ - create: function(data, options) { - var self = this; - return this._model.call('create', [data], { - context: this.get_context() - }).done(function () { - self.trigger('dataset_changed', data, options); - }); - }, - /** - * Saves the provided data in an existing db record - * - * @param {Number|String} id identifier for the record to alter - * @param {Object} data field values to write into the record - * @param {Object} options Dictionary that can contain the following keys: - * - context: The context to use in the server-side call. - * - readonly_fields: Values from readonly fields that were updated by - * on_changes. Only used by the BufferedDataSet to make the o2m work correctly. - * @returns {$.Deferred} - */ - write: function (id, data, options) { - options = options || {}; - var self = this; - return this._model.call('write', [[id], data], { - context: this.get_context(options.context) - }).done(function () { - self.trigger('dataset_changed', id, data, options); - }); - }, - /** - * Deletes an existing record from the database - * - * @param {Number|String} ids identifier of the record to delete - */ - unlink: function(ids) { - var self = this; - return this._model.call('unlink', [ids], { - context: this.get_context() - }).done(function () { - self.trigger('dataset_changed', ids); - }); - }, - /** - * Calls an arbitrary RPC method - * - * @param {String} method name of the method (on the current model) to call - * @param {Array} [args] arguments to pass to the method - * @param {Function} callback - * @param {Function} error_callback - * @returns {$.Deferred} - */ - call: function (method, args) { - return this._model.call(method, args); - }, - /** - * Calls a button method, usually returning some sort of action - * - * @param {String} method - * @param {Array} [args] - * @returns {$.Deferred} - */ - call_button: function (method, args) { - return this._model.call_button(method, args); - }, - /** - * Fetches the "readable name" for records, based on intrinsic rules - * - * @param {Array} ids - * @returns {$.Deferred} - */ - name_get: function(ids) { - return this._model.call('name_get', [ids], {context: this.get_context()}); - }, - /** - * - * @param {String} name name to perform a search for/on - * @param {Array} [domain=[]] filters for the objects returned, OpenERP domain - * @param {String} [operator='ilike'] matching operator to use with the provided name value - * @param {Number} [limit=0] maximum number of matches to return - * @param {Function} callback function to call with name_search result - * @returns {$.Deferred} - */ - name_search: function (name, domain, operator, limit) { - return this._model.call('name_search', { - name: name || '', - args: domain || false, - operator: operator || 'ilike', - context: this._model.context(), - limit: limit || 0 - }); - }, - /** - * @param name - */ - name_create: function(name, context) { - return this._model.call('name_create', [name], {context: this.get_context(context)}); - }, - get_context: function(request_context) { - return this._model.context(request_context); - }, - /** - * Reads or changes sort criteria on the dataset. - * - * If not provided with any argument, serializes the sort criteria to - * an SQL-like form usable by OpenERP's ORM. - * - * If given a field, will set that field as first sorting criteria or, - * if the field is already the first sorting criteria, will reverse it. - * - * @param {String} [field] field to sort on, reverses it (toggle from ASC to DESC) if already the main sort criteria - * @param {Boolean} [force_reverse=false] forces inserting the field as DESC - * @returns {String|undefined} - */ - sort: function (field, force_reverse) { - if (!field) { - return serialize_sort(this._sort); - } - var reverse = force_reverse || (this._sort[0] === field); - this._sort.splice.apply( - this._sort, [0, this._sort.length].concat( - _.without(this._sort, field, '-' + field))); - - this._sort.unshift((reverse ? '-' : '') + field); - return undefined; - }, - /** - * Set the sort criteria on the dataset. - * - * @param {Array} fields_list: list of fields order descriptors, as used by - * Odoo's ORM (such as 'name desc', 'product_id', 'order_date asc') - */ - set_sort: function (fields_list) { - this._sort = deserialize_sort(fields_list); - }, - size: function () { - return this.ids.length; - }, - alter_ids: function(n_ids) { - this.ids = n_ids; - }, - remove_ids: function (ids) { - this.alter_ids(_(this.ids).difference(ids)); - }, - add_ids: function(ids, at) { - var args = [at, 0].concat(_.difference(ids, this.ids)); - this.ids.splice.apply(this.ids, args); - }, - /** - * Resequence records. - * - * @param {Array} ids identifiers of the records to resequence - * @returns {$.Deferred} - */ - resequence: function (ids, options) { - options = options || {}; - return session.rpc('/web/dataset/resequence', { - model: this.model, - ids: ids, - context: pyeval.eval( - 'context', this.get_context(options.context)), - }).then(function (results) { - return results; - }); - }, -}); - -var DataSetStatic = DataSet.extend({ - init: function(parent, model, context, ids) { - this._super(parent, model, context); - // all local records - this.ids = ids || []; - }, - read_slice: function (fields, options) { - options = options || {}; - fields = fields || {}; - var offset = options.offset || 0, - limit = options.limit || false; - var end_pos = limit && limit !== -1 ? offset + limit : this.ids.length; - return this.read_ids(this.ids.slice(offset, end_pos), fields, options); - }, - set_ids: function (ids) { - this.ids = ids; - if (ids.length === 0) { - this.index = null; - } else if (this.index >= ids.length - 1) { - this.index = ids.length - 1; - } - }, - unlink: function(ids) { - this.set_ids(_.without.apply(null, [this.ids].concat(ids))); - this.trigger('unlink', ids); - return $.Deferred().resolve({result: true}); - }, -}); - -var DataSetSearch = DataSet.extend({ - /** - * @constructs instance.web.DataSetSearch - * @extends instance.web.DataSet - * - * @param {Object} parent - * @param {String} model - * @param {Object} context - * @param {Array} domain - */ - init: function(parent, model, context, domain) { - this._super(parent, model, context); - this.domain = domain || []; - this._length = null; - this.ids = []; - this._model = new Model(model, context, domain); - }, - /** - * Read a slice of the records represented by this DataSet, based on its - * domain and context. - * - * @params {Object} options - * @param {Array} [options.fields] fields to read and return, by default all fields are returned - * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain - * @param {Number} [options.offset=0] The index from which selected records should be returned - * @param {Number} [options.limit=null] The maximum number of records to return - * @returns {$.Deferred} - */ - read_slice: function (fields, options) { - options = options || {}; - var self = this; - var q = this._model.query(fields || false) - .filter(options.domain) - .context(options.context) - .offset(options.offset || 0) - .limit(options.limit || false); - q = q.order_by.apply(q, this._sort); - - return this.orderer.add(q.all()).done(function (records) { - // FIXME: not sure about that one, *could* have discarded count - q.count().done(function (count) { self._length = count; }); - self.ids = _(records).pluck('id'); - }); - }, - get_domain: function (other_domain) { - return this._model.domain(other_domain); - }, - alter_ids: function (ids) { - this._super(ids); - if (this.index !== null && this.index >= this.ids.length) { - this.index = this.ids.length > 0 ? this.ids.length - 1 : 0; - } - }, - remove_ids: function (ids) { - var before = this.ids.length; - this._super(ids); - if (this._length) { - this._length -= (before - this.ids.length); - } - }, - add_ids: function(ids, at) { - var before = this.ids.length; - this._super(ids, at); - if(this._length){ - this._length += (this.ids.length - before); - } - }, - unlink: function(ids, callback, error_callback) { - var self = this; - return this._super(ids).done(function() { - self.remove_ids( ids); - self.trigger("dataset_changed", ids, callback, error_callback); - }); - }, - size: function () { - if (this._length !== null) { - return this._length; - } - return this._super(); - } -}); - -var BufferedDataSet = DataSetStatic.extend({ - virtual_id_prefix: "one2many_v_id_", - debug_mode: true, - init: function() { - this._super.apply(this, arguments); - this.reset_ids([]); - this.last_default_get = {}; - this.running_reads = []; - this.mutex = new utils.Mutex(); - }, - default_get: function(fields, options) { - var self = this; - return this._super(fields, options).done(function(res) { - self.last_default_get = _.clone(res); - }); - }, - get_cache: function (id) { - if (!this.cache[id]) { - this.cache[id] = { - 'id': id, - 'values': {}, - 'from_read': {}, - 'changes': {}, - 'readonly_fields': {}, - 'to_create': false, - 'to_delete': false}; - } - return this.cache[id]; - }, - _update_cache: function (id, options) { - // One should call this method after modifying this.from_read, - // this.to_create or this.change. It updates this.cache and - // this.readonly_fields. - var cached = this.get_cache(id); - if (options) { - _.extend(cached.from_read, options.from_read); - _.extend(cached.changes, options.changes); - _.extend(cached.readonly_fields, options.readonly_fields); - if (options.to_create !== undefined) cached.to_create = options.to_create; - if (options.to_delete !== undefined) cached.to_delete = options.to_delete; - } - cached.values = _.extend({'id': id}, cached.from_read, cached.changes, cached.readonly_fields); - return cached; - }, - create: function(data, options) { - var changes = _.extend({}, this.last_default_get, data); - var cached = this._update_cache(_.uniqueId(this.virtual_id_prefix), _.extend({'changes': changes, 'to_create': true}, options)); - this.trigger("dataset_changed", data, options); - return $.Deferred().resolve(cached.id); - }, - write: function (id, data, options) { - var self = this; - var cached = this.get_cache(id); - - // if update after a remove, it's like an add before updating - cached.to_delete = false; - - // apply change - var def = $.Deferred(); - this.mutex.exec(function () { - var dirty = false; - // _.each is broken if a field "length" is present - for (var k in data) { - if (!_.isEqual(data[k], cached.values[k])) { - dirty = true; - if (_.isEqual(data[k], cached.from_read[k])) { // clean changes - delete cached.changes[k]; - } else { - cached.changes[k] = data[k]; - } - } else { - delete data[k]; - } - } - self._update_cache(id, options); - - if (dirty) { - self.trigger("dataset_changed", id, data, options); - } - - return def.resolve(data).promise(); - }); - - return def; - }, - unlink: function(ids, callback, error_callback) { - var self = this; - _.each(ids, function (id) { - self.get_cache(id).to_delete = true; - }); - this.set_ids(_.difference(this.ids, _.pluck(_.filter(this.cache, function (c) {return c.to_delete;}), 'id'))); - this.trigger("dataset_changed", ids, callback, error_callback); - return utils.async_when({result: true}).done(callback); - }, - reset_ids: function(ids, options) { - var self = this; - this.set_ids(ids); - if (!options || !options.keep_read_data) { - this.cache = {}; - } else { - _.each(this.cache, function (cache) { - self._update_cache(cache.id, {'changes': {}, 'to_delete': false}); - }); - } - this.delete_all = false; - this.cancel_read(); - }, - cancel_read: function () { - _.invoke(_.clone(this.running_reads), 'reject'); - }, - read_ids: function (ids, fields, options) { - // read what is necessary from the server to have ids and the given - // fields in this.from_read - var self = this; - var to_get = _.filter(ids, function(id) { - var cache = self.get_cache(id); - return !cache.to_create && _.any(fields, function(x) {return cache.from_read[x] === undefined;}); - }); - options = options || {}; - - var return_records = function() { - var records = _.map(ids, function(id) { - return _.clone(self.get_cache(id).values); - }); - if (self.debug_mode) { - if (_.include(records, undefined)) { - throw "Record not correctly loaded"; - } - } - var sort_fields = self._sort, - compare = function (v1, v2) { - return (v1 < v2) ? -1 - : (v1 > v2) ? 1 - : 0; - }; - // Array.sort is not necessarily stable. We must be careful with this because - // sorting an array where all items are considered equal is a worst-case that - // will randomize the array with an unstable sort! Therefore we must avoid - // sorting if there are no sort_fields (i.e. all items are considered equal) - // See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11 - // http://code.google.com/p/v8/issues/detail?id=90 - if (sort_fields.length) { - records.sort(function (a, b) { - return _.reduce(sort_fields, function (acc, field) { - if (acc) { return acc; } - var sign = 1; - if (field[0] === '-') { - sign = -1; - field = field.slice(1); - } - if(!a[field] && a[field] !== 0){ return sign; } - if(!b[field] && b[field] !== 0){ return (sign == -1) ? 1 : -1; } - //m2o should be searched based on value[1] not based whole value(i.e. [id, value]) - if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){ - return sign * compare(a[field][1], b[field][1]); - } - return sign * compare(a[field], b[field]); - }, 0); - }); - } - return $.when(records); - }; - if(to_get.length > 0) { - var def = $.Deferred(); - self.running_reads.push(def); - def.always(function() { - self.running_reads = _.without(self.running_reads, def); - }); - var _super = this._super; - this.mutex.exec(function () { - if (def.state() !== "pending") return; - _super.call(self, to_get, fields, options).then(_.bind(def.resolve, def), _.bind(def.reject, def)); - return def; - }); - return def.then(function(records) { - _.each(records, function(record, index) { - // add information into from_read - var id = to_get[index]; - self._update_cache(id, _.extend(options, {'from_read': record})); - }); - return return_records(); - }); - } else { - return return_records(); - } - }, - /** - * Invalidates caching of a record in the dataset to ensure the next read - * of that record will hit the server. - * - * Of use when an action is going to remote-alter a record which will then - * need to be reloaded, e.g. action button. - * - * @param {Object} id record to remove from the BDS's cache - */ - evict_record: function (id) { - // Don't evict records which haven't yet been saved: there is no more - // recent data on the server (and there potentially isn't any data), - // and this breaks the assumptions of other methods (that the data - // for new and altered records is both in the cache and in the change - // or to_create collection) - this.get_cache(id).from_read = {}; - this._update_cache(id); - }, - call_button: function (method, args) { - this.evict_record(args[0][0]); - return this._super(method, args); - }, - alter_ids: function(n_ids, options) { - var dirty = !_.isEqual(this.ids, n_ids); - this._super(n_ids, options); - if (dirty) { - this.trigger("dataset_changed", n_ids, options); - } - }, -}); - -BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/; - -var ProxyDataSet = DataSetSearch.extend({ - init: function() { - this._super.apply(this, arguments); - this.create_function = null; - this.write_function = null; - this.read_function = null; - this.default_get_function = null; - this.unlink_function = null; - }, - read_ids: function (ids, fields, options) { - if (this.read_function) { - return this.read_function(ids, fields, options, this._super); - } else { - return this._super.apply(this, arguments); - } - }, - default_get: function(fields, options) { - if (this.default_get_function) { - return this.default_get_function(fields, options, this._super); - } else { - return this._super.apply(this, arguments); - } - }, - create: function(data, options) { - if (this.create_function) { - return this.create_function(data, options, this._super); - } else { - return this._super.apply(this, arguments); - } - }, - write: function (id, data, options) { - if (this.write_function) { - return this.write_function(id, data, options, this._super); - } else { - return this._super.apply(this, arguments); - } - }, - unlink: function(ids) { - if (this.unlink_function) { - return this.unlink_function(ids, this._super); - } else { - return this._super.apply(this, arguments); - } - }, -}); - -var CompoundContext = Class.extend({ - init: function () { - this.__ref = "compound_context"; - this.__contexts = []; - this.__eval_context = null; - var self = this; - _.each(arguments, function(x) { - self.add(x); - }); - }, - add: function (context) { - this.__contexts.push(context); - return this; - }, - set_eval_context: function (eval_context) { - this.__eval_context = eval_context; - return this; - }, - get_eval_context: function () { - return this.__eval_context; - }, - eval: function() { - return pyeval.eval('context', this, undefined, {no_user_context: true}); - }, -}); - -var CompoundDomain = Class.extend({ - init: function () { - this.__ref = "compound_domain"; - this.__domains = []; - this.__eval_context = null; - var self = this; - _.each(arguments, function(x) { - self.add(x); - }); - }, - add: function(domain) { - this.__domains.push(domain); - return this; - }, - set_eval_context: function(eval_context) { - this.__eval_context = eval_context; - return this; - }, - get_eval_context: function() { - return this.__eval_context; - }, - eval: function() { - return pyeval.eval('domain', this); - }, -}); - -function compute_domain (expr, fields) { - if (! (expr instanceof Array)) - return !! expr; - var stack = []; - for (var i = expr.length - 1; i >= 0; i--) { - var ex = expr[i]; - if (ex.length == 1) { - var top = stack.pop(); - switch (ex) { - case '|': - stack.push(stack.pop() || top); - continue; - case '&': - stack.push(stack.pop() && top); - continue; - case '!': - stack.push(!top); - continue; - default: - throw new Error(_.str.sprintf( - _t("Unknown operator %s in domain %s"), - ex, JSON.stringify(expr))); - } - } - - var field = fields[ex[0]]; - if (!field) { - throw new Error(_.str.sprintf( - _t("Unknown field %s in domain %s"), - ex[0], JSON.stringify(expr))); - } - var field_value = field.get_value ? field.get_value() : field.value; - var op = ex[1]; - var val = ex[2]; - - switch (op.toLowerCase()) { - case '=': - case '==': - stack.push(_.isEqual(field_value, val)); - break; - case '!=': - case '<>': - stack.push(!_.isEqual(field_value, val)); - break; - case '<': - stack.push(field_value < val); - break; - case '>': - stack.push(field_value > val); - break; - case '<=': - stack.push(field_value <= val); - break; - case '>=': - stack.push(field_value >= val); - break; - case 'in': - if (!_.isArray(val)) val = [val]; - stack.push(_(val).contains(field_value)); - break; - case 'not in': - if (!_.isArray(val)) val = [val]; - stack.push(!_(val).contains(field_value)); - break; - default: - console.warn( - _t("Unsupported operator %s in domain %s"), - op, JSON.stringify(expr)); - } - } - return _.all(stack, _.identity); -} - -return { - Query: Query, - DataSet: DataSet, - DataSetStatic: DataSetStatic, - DataSetSearch: DataSetSearch, - BufferedDataSet: BufferedDataSet, - ProxyDataSet: ProxyDataSet, - CompoundContext: CompoundContext, - CompoundDomain: CompoundDomain, - compute_domain: compute_domain, - noDisplayContent: "<em class=\"text-warning\">" + _t("Unnamed") + "</em>", -}; - -}); diff --git a/addons/web/static/src/js/framework/data_manager.js b/addons/web/static/src/js/framework/data_manager.js deleted file mode 100644 index 55a4b4da15b..00000000000 --- a/addons/web/static/src/js/framework/data_manager.js +++ /dev/null @@ -1,280 +0,0 @@ -odoo.define('web.DataManager', function (require) { -"use strict"; - -var core = require('web.core'); -var Model = require('web.DataModel'); -var session = require('web.session'); -var utils = require('web.utils'); - -function traverse(tree, f) { - if (f(tree)) { - _.each(tree.children, function(c) { traverse(c, f); }); - } -} - -return core.Class.extend({ - init: function () { - this.Filters = new Model('ir.filters'); - this._init_cache(); - }, - - _init_cache: function () { - this._cache = { - actions: {}, - fields_views: {}, - fields: {}, - filters: {}, - views: {}, - }; - }, - - /** - * Invalidates the whole cache - * Suggestion: could be refined to invalidate some part of the cache - */ - invalidate: function () { - this._init_cache(); - }, - - /** - * Loads an action from its id or xmlid. - * - * @param {int|string} [action_id] the action id or xmlid - * @param {Object} [additional_context] used to load the action - * @return {Deferred} resolved with the action whose id or xmlid is action_id - */ - load_action: function (action_id, additional_context) { - var self = this; - var key = this._gen_key(action_id, additional_context || {}); - - if (!this._cache.actions[key]) { - this._cache.actions[key] = session.rpc("/web/action/load", { - action_id: action_id, - additional_context : additional_context, - }).then(function (action) { - self._cache.actions[key] = action.no_cache ? null : self._cache.actions[key]; - return action; - }, this._invalidate.bind(this, this._cache.actions, key)); - } - - return this._cache.actions[key].then(function (action) { - return $.extend(true, {}, action); - }); - }, - - /** - * Loads various information concerning views: fields_view for each view, - * and optionally filters and fields. - * - * @param {Object} [dataset] the dataset for which the views are loaded - * @param {Array} [views_descr] array of [view_id, view_type] - * @param {Object} [options] dictionnary of various options: - * - options.load_filters: whether or not to load the filters, - * - options.action_id: the action_id (required to load filters), - * - options.load_fields: whether or not to load the fields, - * - options.toolbar: whether or not a toolbar will be displayed, - * @return {Deferred} resolved with the requested views information - */ - load_views: function (dataset, views_descr, options) { - var self = this; - var model = dataset.model; - var context = dataset.get_context(); - var key = this._gen_key(model, views_descr, options || {}, context); - - if (!this._cache.views[key]) { - // Don't load fields or filters if already in cache - options.load_fields = options.load_fields && !this._cache.fields[model]; - var filters_key; - if (options.load_filters) { - filters_key = this._gen_key(model, options.action_id); - options.load_filters = !this._cache.filters[filters_key]; - } - - this._cache.views[key] = dataset.call('load_views', { - views: views_descr, - options: options, - context: context, - }).then(function (result) { - // Postprocess fields_views and insert them into the fields_views cache - result.fields_views = _.mapObject(result.fields_views, self._postprocess_fvg.bind(self)); // FIXME: lazy postprocess? - _.each(views_descr, function (view_descr) { - var toolbar = options.toolbar && view_descr[1] !== 'search'; - var fv_key = self._gen_key(model, view_descr[0], view_descr[1], toolbar, context); - self._cache.fields_views[fv_key] = $.when(result.fields_views[view_descr[1]]); - }); - - // Insert filters, if any, into the filters cache - if (result.filters) { - self._cache.filters[filters_key] = $.when(result.filters); - } - - // Insert fields, if any, into the fields cache - if (result.fields) { - self._cache.fields[model] = $.when(result.fields); - } - - return result.fields_views; - }, this._invalidate.bind(this, this._cache.views, key)); - } - - return this._cache.views[key]; - }, - - /** - * Loads the fields_view of a given model, view_type and optional view_id. - * - * @param {Object} [dataset] the dataset for which the fields_view is loaded - * @param {int} [view_id] the id of the view (optional) - * @param {string} [view_type] the type of the view - * @param {Boolean} [toolbar] whether or not a toolbar will be displayed - * @return {Deferred} resolved with the requested fields_view - */ - load_fields_view: function (dataset, view_id, view_type, toolbar) { - var key = this._gen_key(dataset.model, view_id, view_type, toolbar, dataset.get_context()); - if (!this._cache.fields_views[key]) { - this._cache.fields_views[key] = dataset.call('fields_view_get', { - view_id: view_id, - view_type: view_type, - toolbar: toolbar, - context: dataset.get_context(), - }).then(this._postprocess_fvg.bind(this), this._invalidate.bind(this, this._cache.fields_views, key)); - } - return this._cache.fields_views[key]; - }, - - /** - * Loads the fields of a given model. - * - * @param {Object} [dataset] the dataset for which the fields are loaded - * @return {Deferred} resolved with the requested fields - */ - load_fields: function (dataset) { - if (!this._cache.fields[dataset.model]) { - this._cache.fields[dataset.model] = dataset.call('fields_get', { - context: dataset.get_context(), - }).fail(this._invalidate.bind(this, this._cache.fields, dataset.model)); - } - return this._cache.fields[dataset.model]; - }, - - /** - * Loads the filters of a given model and optional action id. - * - * @param {Object} [dataset] the dataset for which the filters are loaded - * @param {int} [action_id] the id of the action (optional) - * @return {Deferred} resolved with the requested filters - */ - load_filters: function (dataset, action_id) { - var key = this._gen_key(dataset.model, action_id); - if (!this._cache.filters[key]) { - this._cache.filters[key] = this.Filters.call('get_filters', [dataset.model, action_id], { - context: dataset.get_context(), - }).fail(this._invalidate.bind(this, this._cache.filters, key)); - } - return this._cache.filters[key]; - }, - - /** - * Calls 'create_or_replace' on 'ir_filters'. - * - * @param {Object} [filter] the filter description - * @return {Deferred} resolved with the id of the created or replaced filter - */ - create_filter: function (filter) { - var self = this; - return this.Filters - .call('create_or_replace', [filter]) - .then(function (filter_id) { - var key = [ - filter.model_id, - filter.action_id || false, - ].join(','); - self._invalidate(self._cache.filters, key); - return filter_id; - }); - }, - - /** - * Calls 'unlink' on 'ir_filters'. - * - * @param {Object} [filter] the description of the filter to remove - * @return {Deferred} - */ - delete_filter: function (filter) { - var self = this; - return this.Filters - .call('unlink', [filter.id]) - .then(function () { - self._cache.filters = {}; // invalidate cache - }); - }, - - /** - * Private function that postprocesses fields_view (mainly parses the arch attribute) - */ - _postprocess_fvg: function (fields_view) { - var self = this; - - // Parse and process arch - var doc = $.parseXML(fields_view.arch).documentElement; - var fields = fields_view.fields; - fields_view.arch = utils.xml_to_json(doc, (doc.nodeName.toLowerCase() !== 'kanban')); - traverse(fields_view.arch, function(node) { - if (typeof node === 'string') { - return false; - } - if (node.tag === 'field') { - fields[node.attrs.name].__attrs = node.attrs; - if (fields[node.attrs.name].type === 'many2one') { // FIXME: this shouldn't be done here - if (node.attrs.widget === 'statusbar' || node.attrs.widget === 'radio') { - fields[node.attrs.name].__fetch_status = true; - } else if (node.attrs.widget === 'selection') { - fields[node.attrs.name].__fetch_selection = true; - } - } - if (node.attrs.widget === 'many2many_checkboxes') { - fields[node.attrs.name].__fetch_many2manys = true; - } - return false; - } - return node.tag !== 'arch'; - }); - - // Special case for id's - if ('id' in fields_view.fields) { - var id_field = fields_view.fields.id; - id_field.original_type = id_field.type; - id_field.type = 'id'; - } - - // Process inner views (one2manys) - _.each(fields_view.fields, function(field) { - _.each(field.views || {}, function(view) { - self._postprocess_fvg(view); - }); - }); - - return fields_view; - }, - - /** - * Private function that generates a cache key from its arguments - */ - _gen_key: function () { - return _.map(Array.prototype.slice.call(arguments), function (arg) { - if (!arg) { - return false; - } - return _.isObject(arg) ? JSON.stringify(arg) : arg; - }).join(','); - }, - - /** - * Private function that invalidates a cache entry - */ - _invalidate: function (cache, key) { - delete cache[key]; - }, -}); - -}); diff --git a/addons/web/static/src/js/framework/data_model.js b/addons/web/static/src/js/framework/data_model.js deleted file mode 100644 index f722339167f..00000000000 --- a/addons/web/static/src/js/framework/data_model.js +++ /dev/null @@ -1,80 +0,0 @@ -odoo.define('web.DataModel', function (require) { -"use strict"; - -var data = require('web.data'); -var Model = require('web.Model'); -var pyeval = require('web.pyeval'); -var session = require('web.session'); - -Model.include({ - /** - * Fetches a Query instance bound to this model, for searching - * - * @param {Array<String>} [fields] fields to ultimately fetch during the search - * @returns {instance.web.Query} - */ - query: function (fields) { - return new data.Query(this, fields); - }, - /** - * Fetches the model's domain, combined with the provided domain if any - * - * @param {Array} [domain] to combine with the model's internal domain - * @returns {instance.web.CompoundDomain} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain - */ - domain: function (domain) { - if (!domain) { return this._domain; } - return new data.CompoundDomain(this._domain, domain); - }, - /** - * Fetches the combination of the user's context and the domain context, - * combined with the provided context if any - * - * @param {Object} [context] to combine with the model's internal context - * @returns {instance.web.CompoundContext} The union of the user's context and the model's internal context, as well as the provided context if any. In that order. - */ - context: function (context) { - return new data.CompoundContext(session.user_context, this._context, context || {}); - }, - /** - * Call a method (over RPC) on the bound OpenERP model. - * - * @param {String} method name of the method to call - * @param {Array} [args] positional arguments - * @param {Object} [kwargs] keyword arguments - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<>} call result - */ - call: function (method, args, kwargs, options) { - args = args || []; - kwargs = kwargs || {}; - if (!_.isArray(args)) { - // call(method, kwargs) - kwargs = args; - args = []; - } - pyeval.ensure_evaluated(args, kwargs); - var call_kw = '/web/dataset/call_kw/' + this.name + '/' + method; - return session.rpc(call_kw, { - model: this.name, - method: method, - args: args, - kwargs: kwargs - }, options); - }, - call_button: function (method, args) { - pyeval.ensure_evaluated(args, {}); - return session.rpc('/web/dataset/call_button', { - model: this.name, - method: method, - // Should not be necessary anymore. Integrate remote in this? - domain_id: null, - context_id: args.length - 1, - args: args || [] - }); - }, - -}); - -return Model; -}); diff --git a/addons/web/static/src/js/framework/dialog.js b/addons/web/static/src/js/framework/dialog.js deleted file mode 100644 index 6dc65b63281..00000000000 --- a/addons/web/static/src/js/framework/dialog.js +++ /dev/null @@ -1,200 +0,0 @@ -odoo.define('web.Dialog', function (require) { -"use strict"; - -var core = require('web.core'); -var Widget = require('web.Widget'); - -var QWeb = core.qweb; -var _t = core._t; - -/** - A useful class to handle dialogs. - - Attributes: - - $footer: A jQuery element targeting a dom part where buttons can be added. It always exists - during the lifecycle of the dialog. -*/ -var Dialog = Widget.extend({ - /** - Constructor. - - @param {Widget} parent - @param {dictionary} options - - title - - subtitle - - size: one of the following: 'large', 'medium', 'small' - - dialogClass: class to add to the modal-body - - buttons: It must be a list of dictionaries -> text, classes, close, click, disabled - -> If no buttons, a "ok" primary button is added with close = true - -> By default: close = false and classes = 'btn-primary' if only one button and 'btn-default' if many buttons - - $content: Some content to replace this.$el . - */ - init: function (parent, options) { - this._super(parent); - this._opened = $.Deferred(); - - options = _.defaults(options || {}, { - title: _t('Odoo'), subtitle: '', - size: 'large', - dialogClass: '', - $content: false, - buttons: [{text: _t("Ok"), close: true}] - }); - - this.$content = options.$content; - - this.title = options.title; - this.subtitle = options.subtitle; - this.$modal = $(QWeb.render('Dialog', {title: this.title, subtitle: this.subtitle})); - - switch(options.size) { - case 'large': - this.$modal.find('.modal-dialog').addClass('modal-lg'); - break; - case 'small': - this.$modal.find('.modal-dialog').addClass('modal-sm'); - break; - } - - this.dialogClass = options.dialogClass; - this.$footer = this.$modal.find(".modal-footer"); - - this.set_buttons(options.buttons); - - this.$modal.on('hidden.bs.modal', _.bind(this.destroy, this)); - }, - - renderElement: function() { - this._super(); - if(this.$content) { - this.setElement(this.$content); - } - this.$el.addClass('modal-body ' + this.dialogClass); - }, - - set_buttons: function(buttons) { - var self = this; - - self.$footer.empty(); - - _.each(buttons, function(b) { - var text = b.text || ""; - var classes = b.classes || ((buttons.length === 1)? "btn-primary" : "btn-default"); - - var $b = $(QWeb.render('WidgetButton', { widget : { string: text, node: { attrs: {'class': classes, icon: b.icon} }, fa_icon: true }})); - $b.prop('disabled', b.disabled); - $b.on('click', function(e) { - var click_def; - if(b.click) { - click_def = b.click.call(self, e); - } - if(b.close) { - $.when(click_def).always(self.close.bind(self)); - } - }); - self.$footer.append($b); - }); - }, - - set_title: function(title, subtitle) { - this.title = title || ""; - if (subtitle !== undefined) { - this.subtitle = subtitle || ""; - } - - var $title = this.$modal.find('.modal-title').first(); - var $subtitle = $title.find('.o_subtitle').detach(); - $title.html(this.title); - $subtitle.html(this.subtitle).appendTo($title); - - return this; - }, - - opened: function(handler) { - return (handler)? this._opened.then(handler) : this._opened; - }, - - open: function() { - $('.tooltip').remove(); // remove open tooltip if any to prevent them staying when modal is opened - - var self = this; - this.replace(this.$modal.find(".modal-body")).then(function() { - self.$modal.modal('show'); - self._opened.resolve(); - }); - - return self; - }, - - close: function() { - this.$modal.modal('hide'); - }, - - destroy: function(reason) { - if (this.isDestroyed()) { - return; - } - - this.trigger("closed", reason); - - this._super(); - - $('.tooltip').remove(); //remove open tooltip if any to prevent them staying when modal has disappeared - this.$modal.modal('hide'); - this.$modal.remove(); - - setTimeout(function () { // Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal - var modals = $('body > .modal').filter(':visible'); - if(modals.length) { - modals.last().focus(); - $('body').addClass('modal-open'); - } - }, 0); - } -}); - -// static method to open simple alert dialog -Dialog.alert = function (owner, message, options) { - var buttons = [{ - text: _t("Ok"), - close: true, - click: options && options.confirm_callback, - }]; - return new Dialog(owner, _.extend({ - size: 'medium', - buttons: buttons, - $content: $('<div>', { - text: message, - }), - title: _t("Alert"), - }, options)).open(); -}; - -// static method to open simple confirm dialog -Dialog.confirm = function (owner, message, options) { - var buttons = [ - { - text: _t("Ok"), - classes: 'btn-primary', - close: true, - click: options && options.confirm_callback - }, - { - text: _t("Cancel"), - close: true, - click: options && options.cancel_callback - } - ]; - return new Dialog(owner, _.extend({ - size: 'medium', - buttons: buttons, - $content: $('<div>', { - text: message, - }), - title: _t("Confirmation"), - }, options)).open(); -}; - -return Dialog; - -}); diff --git a/addons/web/static/src/js/framework/dom_utils.js b/addons/web/static/src/js/framework/dom_utils.js deleted file mode 100644 index d0beff2988b..00000000000 --- a/addons/web/static/src/js/framework/dom_utils.js +++ /dev/null @@ -1,68 +0,0 @@ -odoo.define('web.dom_utils', function (require) { -"use strict"; - -var core = require('web.core'); - -/** - * Autoresize a $textarea node, by recomputing its height when necessary - * @param {number} [options.min_height] by default, 50. - * @param {Widget} [options.parent] if set, autoresize will listen to some extra - * events to decide when to resize itself. This is useful for widgets that are - * not in the dom when the autoresize is declared. - */ -function autoresize ($textarea, options) { - if ($textarea.data("auto_resize")) { - return; - } - options = options || {}; - - var $fixed_text_area; - var min_height = (options && options.min_height) || 50; - if (!$fixed_text_area) { - $fixed_text_area = $('<textarea disabled>').css({ - position: 'absolute', - opacity: 0, - height: 10, - top: -10000, - left: -10000, - }); - $fixed_text_area.addClass($textarea[0].className); - $fixed_text_area.insertAfter($textarea); - $fixed_text_area.data("auto_resize", true); - } - - var style = window.getComputedStyle($textarea[0], null); - if (style.resize === 'vertical') { - $textarea[0].style.resize = 'none'; - } else if (style.resize === 'both') { - $textarea[0].style.resize = 'horizontal'; - } - resize(); - $textarea.data("auto_resize", true); - - $textarea.on('input focus', resize); - if (options.parent) { - core.bus.on('DOM_updated', options.parent, resize); - core.bus.on('view_shown', options.parent, resize); - } - - function resize () { - var heightOffset; - var style = window.getComputedStyle($textarea[0], null); - if (style.boxSizing === 'content-box') { - heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)); - } else { - heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth); - } - $fixed_text_area.width($textarea.width()); - $fixed_text_area.val($textarea.val()); - var height = $fixed_text_area[0].scrollHeight; - $textarea.css({height: Math.max(height + heightOffset, min_height)}); - } -} - -return { - autoresize: autoresize, -}; - -}); diff --git a/addons/web/static/src/js/framework/domain_utils.js b/addons/web/static/src/js/framework/domain_utils.js deleted file mode 100644 index 8cefb7cabbe..00000000000 --- a/addons/web/static/src/js/framework/domain_utils.js +++ /dev/null @@ -1,19 +0,0 @@ -odoo.define("web.domainUtils", function (require) { -"use strict"; - -var pyeval = require("web.pyeval"); - -function domainToString(domain) { - if (_.isString(domain)) return domain; - return JSON.stringify(domain || []).replace(/false/g, "False").replace(/true/g, "True"); -} -function stringToDomain(domain) { - if (!_.isString(domain)) return domain; - return pyeval.eval("domain", domain || "[]"); -} - -return { - domainToString: domainToString, - stringToDomain: stringToDomain, -}; -}); diff --git a/addons/web/static/src/js/framework/formats.js b/addons/web/static/src/js/framework/formats.js deleted file mode 100644 index 479fa1082cd..00000000000 --- a/addons/web/static/src/js/framework/formats.js +++ /dev/null @@ -1,204 +0,0 @@ -odoo.define('web.formats', function (require) { -"use strict"; - -var translation = require('web.translation'); -var time = require('web.time'); -var utils = require('web.utils'); - -var _t = translation._t; - -/** - * Formats a single atomic value based on a field descriptor - * - * @param {Object} value read from OpenERP - * @param {Object} descriptor union of orm field and view field - * @param {Object} [descriptor.widget] widget to use to display the value - * @param {Object} descriptor.type fallback if no widget is provided, or if the provided widget is unknown - * @param {Object} [descriptor.digits] used for the formatting of floats - * @param {String} [value_if_empty=''] returned if the ``value`` argument is considered empty - */ -function format_value (value, descriptor, value_if_empty) { - var l10n = _t.database.parameters; - var date_format = time.strftime_to_moment_format(l10n.date_format); - var time_format = time.strftime_to_moment_format(l10n.time_format); - // If NaN value, display as with a `false` (empty cell) - if (typeof value === 'number' && isNaN(value)) { - value = false; - } - //noinspection FallthroughInSwitchStatementJS - switch (value) { - case '': - if (descriptor.type === 'char' || descriptor.type === 'text') { - return ''; - } - console.warn('Field', descriptor, 'had an empty string as value, treating as false...'); - return value_if_empty === undefined ? '' : value_if_empty; - case false: - case undefined: - case Infinity: - case -Infinity: - return value_if_empty === undefined ? '' : value_if_empty; - } - switch (descriptor.widget || descriptor.type || (descriptor.field && descriptor.field.type)) { - case 'id': - return value.toString(); - case 'integer': - return utils.insert_thousand_seps( - _.str.sprintf('%d', value)); - case 'monetary': - case 'float': - var digits = descriptor.digits ? descriptor.digits : [69,2]; - digits = typeof digits === "string" ? py.eval(digits) : digits; - var precision = digits[1]; - var formatted = _.str.sprintf('%.' + precision + 'f', value).split('.'); - formatted[0] = utils.insert_thousand_seps(formatted[0]); - return formatted.join(l10n.decimal_point); - case 'float_time': - 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); - case 'many2one': - // name_get value format - return value[1] ? value[1].split("\n")[0] : value[1]; - case 'one2many': - case 'many2many': - if (typeof value === 'string') { - return value; - } - return _.str.sprintf(_t("(%d records)"), value.length); - case 'datetime': - if (typeof(value) == "string") - value = moment(time.auto_str_to_date(value)); - else { - value = moment(value); - } - return value.format(date_format + ' ' + time_format); - case 'date': - if (typeof(value) == "string") - value = moment(time.auto_str_to_date(value)); - else { - value = moment(value); - } - return value.format(date_format); - case 'time': - if (typeof(value) == "string") - value = moment(time.auto_str_to_date(value)); - else { - value = moment(value); - } - return value.format(time_format); - case 'selection': case 'statusbar': - // Each choice is [value, label] - if(_.isArray(value)) { - return value[1]; - } - var result = _(descriptor.selection).detect(function (choice) { - return choice[0] === value; - }); - if (result) { return result[1]; } - return; - default: - return value; - } -} - -function parse_value (value, descriptor, value_if_empty) { - var date_pattern = time.strftime_to_moment_format(_t.database.parameters.date_format), - time_pattern = time.strftime_to_moment_format(_t.database.parameters.time_format); - var date_pattern_wo_zero = date_pattern.replace('MM','M').replace('DD','D'), - time_pattern_wo_zero = time_pattern.replace('HH','H').replace('mm','m').replace('ss','s'); - switch (value) { - case false: - case "": - return value_if_empty === undefined ? false : value_if_empty; - } - var tmp; - switch (descriptor.widget || descriptor.type || (descriptor.field && descriptor.field.type)) { - case 'integer': - do { - tmp = value; - value = value.replace(_t.database.parameters.thousands_sep, ""); - } while(tmp !== value); - tmp = Number(value); - // do not accept not numbers or float values - if (isNaN(tmp) || tmp % 1) - throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value)); - return tmp; - case 'monetary': - case 'float': - var tmp2 = value; - do { - tmp = tmp2; - tmp2 = tmp.replace(_t.database.parameters.thousands_sep, ""); - } while(tmp !== tmp2); - var reformatted_value = tmp.replace(_t.database.parameters.decimal_point, "."); - var parsed = Number(reformatted_value); - if (isNaN(parsed)) - throw new Error(_.str.sprintf(_t("'%s' is not a correct float"), value)); - return parsed; - case 'float_time': - 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 * parse_value(value, {type: "float"}); - var hours = parse_value(float_time_pair[0], {type: "integer"}); - var minutes = parse_value(float_time_pair[1], {type: "integer"}); - return factor * (hours + (minutes / 60)); - case 'progressbar': - return parse_value(value, {type: "float"}); - case 'datetime': - var datetime = moment(value, [date_pattern + ' ' + time_pattern, date_pattern_wo_zero + ' ' + time_pattern_wo_zero, moment.ISO_8601], true); - if (datetime.isValid() && datetime.year() >= 1900) - return time.datetime_to_str(datetime.toDate()); - datetime = moment(value, [date_pattern + ' ' + time_pattern, date_pattern_wo_zero + ' ' + time_pattern_wo_zero, moment.ISO_8601]); - if (datetime.isValid()) { - if (datetime.year() === 0) { - datetime.year(moment.utc().year()); - } - if (datetime.year() >= 1900) { - return time.datetime_to_str(datetime.toDate()); - } - } - throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value)); - case 'date': - var date = moment(value, [date_pattern, date_pattern_wo_zero, moment.ISO_8601], true); - if (date.isValid() && date.year() >= 1900) - return time.date_to_str(date.toDate()); - date = moment(value, [date_pattern, date_pattern_wo_zero, moment.ISO_8601]); - if (date.isValid()) { - if (date.year() === 0) { - date.year(moment.utc().year()); - } - if (date.year() >= 1900) { - return time.date_to_str(date.toDate()); - } - } - throw new Error(_.str.sprintf(_t("'%s' is not a correct date"), value)); - case 'time': - var _time = moment(value, [time_pattern, time_pattern_wo_zero, moment.ISO_8601], true); - if (_time.isValid()) - return time.time_to_str(_time.toDate()); - throw new Error(_.str.sprintf(_t("'%s' is not a correct time"), value)); - } - return value; -} - -return { - format_value: format_value, - parse_value: parse_value, -}; - -}); diff --git a/addons/web/static/src/js/framework/local_storage.js b/addons/web/static/src/js/framework/local_storage.js deleted file mode 100644 index 6cf82e87752..00000000000 --- a/addons/web/static/src/js/framework/local_storage.js +++ /dev/null @@ -1,34 +0,0 @@ -odoo.define('web.local_storage', function (require) { -'use strict'; - -var Class = require('web.Class'); - -// Compatibility with private browsing in Safari -// Use dict in case of local storage is disabled -var localStorage = window.localStorage; -try { - var uid = new Date(); - localStorage.setItem(uid, uid); - localStorage.removeItem(uid); -} catch (exception) { - var storage = {}; - var RamStorage = Class.extend({ - setItem: function(key, value) { - storage[key] = value; - }, - getItem: function(key) { - return storage[key]; - }, - removeItem: function(key) { - delete storage[key]; - }, - clear: function() { - storage = {}; - }, - }); - localStorage = new RamStorage(); -} - -return localStorage; - -}); diff --git a/addons/web/static/src/js/framework/misc.js b/addons/web/static/src/js/framework/misc.js deleted file mode 100644 index e083ee25b22..00000000000 --- a/addons/web/static/src/js/framework/misc.js +++ /dev/null @@ -1,438 +0,0 @@ -odoo.define('web.framework', function (require) { -"use strict"; - -var core = require('web.core'); -var crash_manager = require('web.crash_manager'); -var ajax = require('web.ajax'); -var Widget = require('web.Widget'); - -var _t = core._t; - -var messages_by_seconds = function() { - return [ - [0, _t("Loading...")], - [20, _t("Still loading...")], - [60, _t("Still loading...<br />Please be patient.")], - [120, _t("Don't leave yet,<br />it's still loading...")], - [300, _t("You may not believe it,<br />but the application is actually loading...")], - [420, _t("Take a minute to get a coffee,<br />because it's loading...")], - [3600, _t("Maybe you should consider reloading the application by pressing F5...")] - ]; -}; - -var Throbber = Widget.extend({ - template: "Throbber", - start: function() { - this.start_time = new Date().getTime(); - this.act_message(); - }, - act_message: function() { - var self = this; - setTimeout(function() { - if (self.isDestroyed()) - return; - var seconds = (new Date().getTime() - self.start_time) / 1000; - var mes; - _.each(messages_by_seconds(), function(el) { - if (seconds >= el[0]) - mes = el[1]; - }); - self.$(".oe_throbber_message").html(mes); - self.act_message(); - }, 1000); - }, -}); - -/** Setup blockui */ -if ($.blockUI) { - $.blockUI.defaults.baseZ = 1100; - $.blockUI.defaults.message = '<div class="openerp oe_blockui_spin_container" style="background-color: transparent;">'; - $.blockUI.defaults.css.border = '0'; - $.blockUI.defaults.css["background-color"] = ''; -} - - -var throbbers = []; - -function blockUI () { - var tmp = $.blockUI.apply($, arguments); - var throbber = new Throbber(); - throbbers.push(throbber); - throbber.appendTo($(".oe_blockui_spin_container")); - $('body').addClass('o_ui_blocked'); - return tmp; -} - -function unblockUI () { - _.invoke(throbbers, 'destroy'); - throbbers = []; - $('body').removeClass('o_ui_blocked'); - return $.unblockUI.apply($, arguments); -} - -/** - * Redirect to url by replacing window.location - * If wait is true, sleep 1s and wait for the server i.e. after a restart. - */ -function redirect (url, wait) { - // Dont display a dialog if some xmlhttprequest are in progress - crash_manager.disable(); - - var load = function() { - var old = "" + window.location; - var old_no_hash = old.split("#")[0]; - var url_no_hash = url.split("#")[0]; - location.assign(url); - if (old_no_hash === url_no_hash) { - location.reload(true); - } - }; - - var wait_server = function() { - ajax.rpc("/web/webclient/version_info", {}).done(load).fail(function() { - setTimeout(wait_server, 250); - }); - }; - - if (wait) { - setTimeout(wait_server, 1000); - } else { - load(); - } -} - -// * Client action to reload the whole interface. -// * If params.menu_id, it opens the given menu entry. -// * If params.wait, reload will wait the openerp server to be reachable before reloading - -function Reload(parent, action) { - var params = action.params || {}; - var menu_id = params.menu_id || false; - var l = window.location; - - var sobj = $.deparam(l.search.substr(1)); - if (params.url_search) { - sobj = _.extend(sobj, params.url_search); - } - var search = '?' + $.param(sobj); - - var hash = l.hash; - if (menu_id) { - hash = "#menu_id=" + menu_id; - } - var url = l.protocol + "//" + l.host + l.pathname + search + hash; - - redirect(url, params.wait); -} - -core.action_registry.add("reload", Reload); - - -/** - * Client action to go back home. - */ -function Home (parent, action) { - var url = '/' + (window.location.search || ''); - redirect(url, action && action.params && action.params.wait); -} -core.action_registry.add("home", Home); - -/** - * Client action to go back in breadcrumb history. - * If can't go back in history stack, will go back to home. - */ -function HistoryBack (parent) { - parent.history_back().fail(function () { - Home(parent); - }); -} -core.action_registry.add("history_back", HistoryBack); - -function login() { - redirect('/web/login'); -} -core.action_registry.add("login", login); - -function logout() { - redirect('/web/session/logout'); - return $.Deferred(); -} -core.action_registry.add("logout", logout); - -/** - * Client action to refresh the session context (making sure - * HTTP requests will have the right one) then reload the - * whole interface. - */ -function ReloadContext (parent, action) { - // side-effect of get_session_info is to refresh the session context - ajax.rpc("/web/session/get_session_info", {}).then(function() { - Reload(parent, action); - }); -} -core.action_registry.add("reload_context", ReloadContext); - - -// nvd3 customization -//------------------------------------------------------------------------- -if ('nv' in window) { - nv.dev = false; // sets nvd3 library in production mode - - // monkey patch nvd3 to allow removing eventhandler on windowresize events - // see https://github.com/novus/nvd3/pull/396 for more details - - // Adds a resize listener to the window. - nv.utils.onWindowResize = function(fun) { - if (fun === null) return; - window.addEventListener('resize', fun); - }; - - // Backwards compatibility with current API. - nv.utils.windowResize = nv.utils.onWindowResize; - - // Removes a resize listener from the window. - nv.utils.offWindowResize = function(fun) { - if (fun === null) return; - window.removeEventListener('resize', fun); - }; - - // monkey patch nvd3 to prevent crashes when user changes view and nvd3 tries - // to remove tooltips after 500 ms... seriously nvd3, what were you thinking? - nv.tooltip.cleanup = function () { - $('.nvtooltip').remove(); - }; - - // monkey patch nvd3 to prevent it to display a tooltip (position: absolute) with - // a negative `top`; with this patch the highest tooltip's position is still in the - // graph - var originalCalcTooltipPosition = nv.tooltip.calcTooltipPosition; - nv.tooltip.calcTooltipPosition = function () { - var container = originalCalcTooltipPosition.apply(this, arguments); - container.style.top = container.style.top.split('px')[0] < 0 ? 0 + 'px' : container.style.top; - return container; - }; -} - -// Bootstrap customization -//------------------------------------------------------------------------- -/* Bootstrap defaults overwrite */ -$.fn.tooltip.Constructor.DEFAULTS.placement = 'auto top'; -$.fn.tooltip.Constructor.DEFAULTS.html = true; -$.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover focus click'; -$.fn.tooltip.Constructor.DEFAULTS.container = 'body'; -$.fn.tooltip.Constructor.DEFAULTS.delay = { show: 1000, hide: 0 }; -//overwrite bootstrap tooltip method to prevent showing 2 tooltip at the same time -var bootstrap_show_function = $.fn.tooltip.Constructor.prototype.show; -$.fn.modal.Constructor.prototype.enforceFocus = function () { }; -$.fn.tooltip.Constructor.prototype.show = function () { - $('.tooltip').remove(); - //the following fix the bug when using placement - //auto and the parent element does not exist anymore resulting in - //an error. This should be remove once we updade bootstrap to a version that fix the bug - //edit: bug has been fixed here : https://github.com/twbs/bootstrap/pull/13752 - var e = $.Event('show.bs.' + this.type); - var inDom = $.contains(document.documentElement, this.$element[0]); - if (e.isDefaultPrevented() || !inDom) return; - return bootstrap_show_function.call(this); -}; - -// jquery customization -//------------------------------------------------------------------------- -jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) { - return function( elem ) { - return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0; - }; -}); - -/** Custom jQuery plugins */ -$.fn.getAttributes = function() { - var o = {}; - if (this.length) { - for (var attr, i = 0, attrs = this[0].attributes, l = attrs.length; i < l; i++) { - attr = attrs.item(i); - o[attr.nodeName] = attr.value; - } - } - return o; -}; -$.fn.openerpClass = function(additionalClass) { - // This plugin should be applied on top level elements - additionalClass = additionalClass || ''; - if (!!$.browser.msie) { - additionalClass += ' openerp_ie'; - } - return this.each(function() { - $(this).addClass('openerp ' + additionalClass); - }); -}; -$.fn.openerpBounce = function() { - return this.each(function() { - $(this).css('box-sizing', 'content-box').effect('bounce', {distance: 18, times: 5}, 250); - }); -}; - -// jquery autocomplete tweak to allow html and classnames -var proto = $.ui.autocomplete.prototype, - initSource = proto._initSource; - -function filter( array, term ) { - var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); - return $.grep( array, function(value_) { - return matcher.test( $( "<div>" ).html( value_.label || value_.value || value_ ).text() ); - }); -} - -$.extend( proto, { - _initSource: function() { - if ( this.options.html && $.isArray(this.options.source) ) { - this.source = function( request, response ) { - response( filter( this.options.source, request.term ) ); - }; - } else { - initSource.call( this ); - } - }, - - _renderItem: function( ul, item) { - return $( "<li></li>" ) - .data( "item.autocomplete", item ) - .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) ) - .appendTo( ul ) - .addClass(item.classname); - } -}); - -/** - * Private function to notify that something has been attached in the DOM - * @param {htmlString or Element or Array or jQuery} [content] the content that - * has been attached in the DOM - * @params {Array} [callbacks] array of {widget: w, callback_args: args} such - * that on_attach_callback() will be called on each w with arguments args - */ -function _notify (content, callbacks) { - _.each(callbacks, function(c) { - if (c.widget && c.widget.on_attach_callback) { - c.widget.on_attach_callback(c.callback_args); - } - }); - core.bus.trigger('DOM_updated', content); -} -/** - * Appends content in a jQuery object and optionnally triggers an event - * @param {jQuery} [$target] the node where content will be appended - * @param {htmlString or Element or Array or jQuery} [content] DOM element, - * array of elements, HTML string or jQuery object to append to $target - * @param {Boolean} [options.in_DOM] true if $target is in the DOM - * @param {Array} [options.callbacks] array of objects describing the callbacks - * to perform (see _notify for a complete description) - */ -function append ($target, content, options) { - $target.append(content); - if (options && options.in_DOM) { - _notify(content, options.callbacks); - } -} -/** - * Prepends content in a jQuery object and optionnally triggers an event - * @param {jQuery} [$target] the node where content will be prepended - * @param {htmlString or Element or Array or jQuery} [content] DOM element, - * array of elements, HTML string or jQuery object to prepend to $target - * @param {Boolean} [options.in_DOM] true if $target is in the DOM - * @param {Array} [options.callbacks] array of objects describing the callbacks - * to perform (see _notify for a complete description) - */ -function prepend ($target, content, options) { - $target.prepend(content); - if (options && options.in_DOM) { - _notify(content, options.callbacks); - } -} - -/** - * Detaches widgets from the DOM and performs their on_detach_callback() - * @param {Array} [to_detach] array of {widget: w, callback_args: args} such - * that w.$el will be detached and w.on_detach_callback(args) will be called - * @param {jQuery} [options.$to_detach] if given, detached instead of widgets' $el - * @return {jQuery} the detached elements - */ -function detach (to_detach, options) { - _.each(to_detach, function(d) { - if (d.widget.on_detach_callback) { - d.widget.on_detach_callback(d.callback_args); - } - }); - var $to_detach = options && options.$to_detach; - if (!$to_detach) { - $to_detach = $(_.map(to_detach, function(d) { - return d.widget.el; - })); - } - return $to_detach.detach(); -} - -/** - * Returns the distance between a DOM element and the top-left corner of the window - * @param {element} [e] the DOM element - * @return {Object} the left and top distances in pixels - */ -function getPosition(e) { - var position = {left: 0, top: 0}; - while (e) { - position.left += e.offsetLeft; - position.top += e.offsetTop; - e = e.offsetParent; - } - return position; -} - -return { - blockUI: blockUI, - unblockUI: unblockUI, - redirect: redirect, - append: append, - prepend: prepend, - detach: detach, - getPosition: getPosition, -}; - -}); - -odoo.define('web.IFrameWidget', function (require) { -"use strict"; - -var Widget = require('web.Widget'); - -/** - * Generic widget to create an iframe that listens for clicks - * - * It should be extended by overwritting the methods: - * init: function(parent) { - * this._super(parent, <url_of_iframe> - * }, - * iframe_clicked: function(e){ - * filter the clicks you want to use and apply - * an action on it - * } - */ -var IFrameWidget = Widget.extend({ - tagName: 'iframe', - init: function(parent, url) { - this._super(parent); - this.url = url; - }, - start: function() { - this.$el.css({height: '100%', width: '100%', border: 0}); - this.$el.attr({src: this.url}); - this.$el.on("load", this.bind_events.bind(this)); - return this._super(); - }, - bind_events: function(){ - this.$el.contents().click(this.iframe_clicked.bind(this)); - }, - iframe_clicked: function(e){ - } -}); - -return IFrameWidget; - -}); diff --git a/addons/web/static/src/js/framework/mixins.js b/addons/web/static/src/js/framework/mixins.js deleted file mode 100644 index 05d4105e466..00000000000 --- a/addons/web/static/src/js/framework/mixins.js +++ /dev/null @@ -1,367 +0,0 @@ -odoo.define('web.mixins', function (require) { -"use strict"; - -var Class = require('web.Class'); -var utils = require('web.utils'); - -/** - * Mixin to structure objects' life-cycles folowing a parent-children - * relationship. Each object can a have a parent and multiple children. - * When an object is destroyed, all its children are destroyed too releasing - * any resource they could have reserved before. - */ -var ParentedMixin = { - __parentedMixin : true, - init: function() { - this.__parentedDestroyed = false; - this.__parentedChildren = []; - this.__parentedParent = null; - }, - /** - * Set the parent of the current object. When calling this method, the - * parent will also be informed and will return the current object - * when its getChildren() method is called. If the current object did - * already have a parent, it is unregistered before, which means the - * previous parent will not return the current object anymore when its - * getChildren() method is called. - */ - setParent : function(parent) { - if (this.getParent()) { - if (this.getParent().__parentedMixin) { - this.getParent().__parentedChildren = _.without(this - .getParent().getChildren(), this); - } - } - this.__parentedParent = parent; - if (parent && parent.__parentedMixin) { - parent.__parentedChildren.push(this); - } - }, - /** - * Return the current parent of the object (or null). - */ - getParent : function() { - return this.__parentedParent; - }, - /** - * Return a list of the children of the current object. - */ - getChildren : function() { - return _.clone(this.__parentedChildren); - }, - /** - * Returns true if destroy() was called on the current object. - */ - isDestroyed : function() { - return this.__parentedDestroyed; - }, - /** - Utility method to only execute asynchronous actions if the current - object has not been destroyed. - - @param {$.Deferred} promise The promise representing the asynchronous - action. - @param {bool} [reject=false] If true, the returned promise will be - rejected with no arguments if the current - object is destroyed. If false, the - returned promise will never be resolved - or rejected. - @returns {$.Deferred} A promise that will mirror the given promise if - everything goes fine but will either be rejected - with no arguments or never resolved if the - current object is destroyed. - */ - alive: function(promise, reject) { - var self = this; - return $.Deferred(function (def) { - promise.then(function () { - if (!self.isDestroyed()) { - def.resolve.apply(def, arguments); - } - }, function () { - if (!self.isDestroyed()) { - def.reject.apply(def, arguments); - } - }).always(function () { - if (reject) { - // noop if def already resolved or rejected - def.reject(); - } - // otherwise leave promise in limbo - }); - }).promise(); - }, - /** - * Inform the object it should destroy itself, releasing any - * resource it could have reserved. - */ - destroy : function() { - _.each(this.getChildren(), function(el) { - el.destroy(); - }); - this.setParent(undefined); - this.__parentedDestroyed = true; - }, - /** - * Find the closest ancestor matching predicate - */ - findAncestor: function (predicate) { - var ancestor = this; - while (ancestor && !(predicate(ancestor)) && ancestor.getParent) { - ancestor = ancestor.getParent(); - } - return ancestor; - }, -}; - -function OdooEvent (target, name, data) { - this.target = target; - this.name = name; - this.data = Object.create(null); - _.extend(this.data, data); - this.stopped = false; -} - -OdooEvent.prototype.stop_propagation = function () { - this.stopped = true; -}; - -OdooEvent.prototype.is_stopped = function () { - return this.stopped; -}; - -/** - * Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead. - * - * This class just handle the dispatching of events, it is not meant to be extended, - * nor used directly. All integration with parenting and automatic unregistration of - * events is done in EventDispatcherMixin. - * - * Copyright notice for the following Class: - * - * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. - * Backbone may be freely distributed under the MIT license. - * For all details and documentation: - * http://backbonejs.org - * - */ -var Events = Class.extend({ - on : function(events, callback, context) { - var ev; - events = events.split(/\s+/); - var calls = this._callbacks || (this._callbacks = {}); - while ((ev = events.shift())) { - var list = calls[ev] || (calls[ev] = {}); - var tail = list.tail || (list.tail = list.next = {}); - tail.callback = callback; - tail.context = context; - list.tail = tail.next = {}; - } - return this; - }, - - off : function(events, callback, context) { - var ev, calls, node; - if (!events) { - delete this._callbacks; - } else if ((calls = this._callbacks)) { - events = events.split(/\s+/); - while ((ev = events.shift())) { - node = calls[ev]; - delete calls[ev]; - if (!callback || !node) - continue; - while ((node = node.next) && node.next) { - if (node.callback === callback - && (!context || node.context === context)) - continue; - this.on(ev, node.callback, node.context); - } - } - } - return this; - }, - - callbackList: function() { - var lst = []; - _.each(this._callbacks || {}, function(el, eventName) { - var node = el; - while ((node = node.next) && node.next) { - lst.push([eventName, node.callback, node.context]); - } - }); - return lst; - }, - - trigger : function(events) { - var event, node, calls, tail, args, all, rest; - if (!(calls = this._callbacks)) - return this; - all = calls.all; - (events = events.split(/\s+/)).push(null); - // Save references to the current heads & tails. - while ((event = events.shift())) { - if (all) - events.push({ - next : all.next, - tail : all.tail, - event : event - }); - if (!(node = calls[event])) - continue; - events.push({ - next : node.next, - tail : node.tail - }); - } - rest = Array.prototype.slice.call(arguments, 1); - while ((node = events.pop())) { - tail = node.tail; - args = node.event ? [ node.event ].concat(rest) : rest; - while ((node = node.next) !== tail) { - node.callback.apply(node.context || this, args); - } - } - return this; - } -}); - -/** - Mixin containing an event system. Events are also registered by specifying the target object - (the object which will receive the event when it is raised). Both the event-emitting object - and the target object store or reference to each other. This is used to correctly remove all - reference to the event handler when any of the object is destroyed (when the destroy() method - from ParentedMixin is called). Removing those references is necessary to avoid memory leak - and phantom events (events which are raised and sent to a previously destroyed object). -*/ -var EventDispatcherMixin = _.extend({}, ParentedMixin, { - __eventDispatcherMixin: true, - init: function() { - ParentedMixin.init.call(this); - this.__edispatcherEvents = new Events(); - this.__edispatcherRegisteredEvents = []; - }, - on: function(events, dest, func) { - var self = this; - if (typeof func !== "function") { - throw new Error("Event handler must be a function."); - } - events = events.split(/\s+/); - _.each(events, function(eventName) { - self.__edispatcherEvents.on(eventName, func, dest); - if (dest && dest.__eventDispatcherMixin) { - dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self}); - } - }); - return this; - }, - off: function(events, dest, func) { - var self = this; - events = events.split(/\s+/); - _.each(events, function(eventName) { - self.__edispatcherEvents.off(eventName, func, dest); - if (dest && dest.__eventDispatcherMixin) { - dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) { - return !(el.name === eventName && el.func === func && el.source === self); - }); - } - }); - return this; - }, - once: function(events, dest, func) { - // similar to this.on(), but func is executed only once - var self = this - if (typeof func !== "function") { - throw new Error("Event handler must be a function."); - } - self.on(events, dest, function what() { - func.apply(this, arguments); - self.off(events, dest, what); - }); - }, - trigger: function() { - this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments); - return this; - }, - trigger_up: function(name, info) { - var event = new OdooEvent(this, name, info); - this._trigger_up(event); - }, - _trigger_up: function(event) { - var parent; - this.__edispatcherEvents.trigger(event.name, event); - if (!event.is_stopped() && (parent = this.getParent())) { - parent._trigger_up(event); - } - }, - destroy: function() { - var self = this; - _.each(this.__edispatcherRegisteredEvents, function(event) { - event.source.__edispatcherEvents.off(event.name, event.func, self); - }); - this.__edispatcherRegisteredEvents = []; - _.each(this.__edispatcherEvents.callbackList(), function(cal) { - this.off(cal[0], cal[2], cal[1]); - }, this); - this.__edispatcherEvents.off(); - ParentedMixin.destroy.call(this); - } -}); - -var PropertiesMixin = _.extend({}, EventDispatcherMixin, { - init: function() { - EventDispatcherMixin.init.call(this); - this.__getterSetterInternalMap = {}; - }, - set: function(arg1, arg2, arg3) { - var map; - var options; - if (typeof arg1 === "string") { - map = {}; - map[arg1] = arg2; - options = arg3 || {}; - } else { - map = arg1; - options = arg2 || {}; - } - var self = this; - var changed = false; - _.each(map, function(val, key) { - var tmp = self.__getterSetterInternalMap[key]; - if (tmp === val) - return; - // seriously, why are you doing this? it is obviously a stupid design. - // the properties mixin should not be concerned with handling fields details. - // this also has the side effect of introducing a dependency on utils. Todo: - // remove this, or move it elsewhere. Also, learn OO programming. - if (key === 'value' && self.field && self.field.type === 'float' && tmp && val){ - var digits = self.field.digits; - if (_.isArray(digits)) { - if (utils.float_is_zero(tmp - val, digits[1])) { - return; - } - } - } - changed = true; - self.__getterSetterInternalMap[key] = val; - if (! options.silent) - self.trigger("change:" + key, self, { - oldValue: tmp, - newValue: val - }); - }); - if (changed) - self.trigger("change", self); - }, - get: function(key) { - return this.__getterSetterInternalMap[key]; - } -}); - -return { - ParentedMixin: ParentedMixin, - EventDispatcherMixin: EventDispatcherMixin, - PropertiesMixin: PropertiesMixin, -}; - -}); diff --git a/addons/web/static/src/js/framework/model.js b/addons/web/static/src/js/framework/model.js deleted file mode 100644 index 377ff1a1757..00000000000 --- a/addons/web/static/src/js/framework/model.js +++ /dev/null @@ -1,60 +0,0 @@ -odoo.define('web.Model', function (require) { -"use strict"; - -var Class = require('web.Class'); -var session = require('web.session'); - -var Model = Class.extend({ - /** - new openerp.web.Model(name[, context[, domain]]) - - @constructs instance.web.Model - @extends instance.web.Class - - @param {String} name name of the OpenERP model this object is bound to - @param {Object} [context] - @param {Array} [domain] - */ - init: function(name, context, domain) { - this.name = name; - this._context = context || {}; - this._domain = domain || []; - }, - /** - * @deprecated does not allow to specify kwargs, directly use call() instead - */ - get_func: function (method_name) { - var self = this; - return function () { - return self.call(method_name, _.toArray(arguments)); - }; - }, - /** - * Call a method (over RPC) on the bound OpenERP model. - * - * @param {String} method name of the method to call - * @param {Array} [args] positional arguments - * @param {Object} [kwargs] keyword arguments - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<>} call result - */ - call: function (method, args, kwargs, options) { - args = args || []; - kwargs = kwargs || {}; - if (!_.isArray(args)) { - // call(method, kwargs) - kwargs = args; - args = []; - } - var call_kw = '/web/dataset/call_kw/' + this.name + '/' + method; - return session.rpc(call_kw, { - model: this.name, - method: method, - args: args, - kwargs: kwargs - }, options); - }, -}); - -return Model; -}); diff --git a/addons/web/static/src/js/framework/pyeval.js b/addons/web/static/src/js/framework/pyeval.js deleted file mode 100644 index bcab0b0717f..00000000000 --- a/addons/web/static/src/js/framework/pyeval.js +++ /dev/null @@ -1,1061 +0,0 @@ -odoo.define('web.pyeval', function (require) { -"use strict"; - -var core = require('web.core'); -var session = require('web.session'); -var utils = require('web.utils'); - -var _t = core._t; -var py = window.py; // to silence linters -/* - * py.js helpers and setup - */ - -var obj = function () {}; -obj.prototype = py.object; -var asJS = function (arg) { - if (arg instanceof obj) { - return arg.toJSON(); - } - return arg; -}; - -var datetime = py.PY_call(py.object); - -var zero = py.float.fromJSON(0); - -// Port from pypy/lib_pypy/datetime.py -var DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; -var DAYS_BEFORE_MONTH = [null]; -var dbm = 0; - -for (var i=1; i<DAYS_IN_MONTH.length; ++i) { - DAYS_BEFORE_MONTH.push(dbm); - dbm += DAYS_IN_MONTH[i]; -} - -function is_leap (year) { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); -} - -function days_before_year (year) { - var y = year - 1; - return y*365 + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400); -} - -function days_in_month (year, month) { - if (month === 2 && is_leap(year)) { - return 29; - } - return DAYS_IN_MONTH[month]; -} - -function days_before_month (year, month) { - var post_leap_feb = month > 2 && is_leap(year); - return DAYS_BEFORE_MONTH[month] + (post_leap_feb ? 1 : 0); -} - -function ymd2ord (year, month, day) { - var dim = days_in_month(year, month); - if (!(1 <= day && day <= dim)) { - throw new Error("ValueError: day must be in 1.." + dim); - } - return days_before_year(year) + - days_before_month(year, month) + - day; -} - -var DI400Y = days_before_year(401); -var DI100Y = days_before_year(101); -var DI4Y = days_before_year(5); - -function ord2ymd (n) { - --n; - var n400, n100, n4, n1, n0; - utils.divmod(n, DI400Y, function (_n400, n) { - n400 = _n400; - utils.divmod(n, DI100Y, function (_n100, n) { - n100 = _n100; - utils.divmod(n, DI4Y, function (_n4, n) { - n4 = _n4; - utils.divmod(n, 365, function (_n1, n) { - n1 = _n1; - n0 = n; - }); - }); - }); - }); - - n = n0; - var year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1; - if (n1 == 4 || n100 == 100) { - utils.assert(n0 === 0); - return { - year: year - 1, - month: 12, - day: 31 - }; - } - - var leapyear = n1 === 3 && (n4 !== 24 || n100 == 3); - utils.assert(leapyear == is_leap(year)); - var month = (n + 50) >> 5; - var preceding = DAYS_BEFORE_MONTH[month] + ((month > 2 && leapyear) ? 1 : 0); - if (preceding > n) { - --month; - preceding -= DAYS_IN_MONTH[month] + ((month === 2 && leapyear) ? 1 : 0); - } - n -= preceding; - return { - year: year, - month: month, - day: n+1 - }; -} - -/** - * Converts the stuff passed in into a valid date, applying overflows as needed - */ -function tmxxx(year, month, day, hour, minute, second, microsecond) { - hour = hour || 0; minute = minute || 0; second = second || 0; - microsecond = microsecond || 0; - - if (microsecond < 0 || microsecond > 999999) { - utils.divmod(microsecond, 1000000, function (carry, ms) { - microsecond = ms; - second += carry; - }); - } - if (second < 0 || second > 59) { - utils.divmod(second, 60, function (carry, s) { - second = s; - minute += carry; - }); - } - if (minute < 0 || minute > 59) { - utils.divmod(minute, 60, function (carry, m) { - minute = m; - hour += carry; - }); - } - if (hour < 0 || hour > 23) { - utils.divmod(hour, 24, function (carry, h) { - hour = h; - day += carry; - }); - } - // That was easy. Now it gets muddy: the proper range for day - // can't be determined without knowing the correct month and year, - // but if day is, e.g., plus or minus a million, the current month - // and year values make no sense (and may also be out of bounds - // themselves). - // Saying 12 months == 1 year should be non-controversial. - if (month < 1 || month > 12) { - utils.divmod(month-1, 12, function (carry, m) { - month = m + 1; - year += carry; - }); - } - // Now only day can be out of bounds (year may also be out of bounds - // for a datetime object, but we don't care about that here). - // If day is out of bounds, what to do is arguable, but at least the - // method here is principled and explainable. - var dim = days_in_month(year, month); - if (day < 1 || day > dim) { - // Move day-1 days from the first of the month. First try to - // get off cheap if we're only one day out of range (adjustments - // for timezone alone can't be worse than that). - if (day === 0) { - --month; - if (month > 0) { - day = days_in_month(year, month); - } else { - --year; month=12; day=31; - } - } else if (day == dim + 1) { - ++month; - day = 1; - if (month > 12) { - month = 1; - ++year; - } - } else { - var r = ord2ymd(ymd2ord(year, month, 1) + (day - 1)); - year = r.year; - month = r.month; - day = r.day; - } - } - return { - year: year, - month: month, - day: day, - hour: hour, - minute: minute, - second: second, - microsecond: microsecond - }; -} - -datetime.timedelta = py.type('timedelta', null, { - __init__: function () { - var args = py.PY_parseArgs(arguments, [ - ['days', zero], ['seconds', zero], ['microseconds', zero], - ['milliseconds', zero], ['minutes', zero], ['hours', zero], - ['weeks', zero] - ]); - - var d = 0, s = 0, m = 0; - var days = args.days.toJSON() + args.weeks.toJSON() * 7; - var seconds = args.seconds.toJSON() - + args.minutes.toJSON() * 60 - + args.hours.toJSON() * 3600; - var microseconds = args.microseconds.toJSON() - + args.milliseconds.toJSON() * 1000; - - // Get rid of all fractions, and normalize s and us. - // Take a deep breath <wink>. - var daysecondsfrac = utils.modf(days, function (dayfrac, days) { - d = days; - if (dayfrac) { - return utils.modf(dayfrac * 24 * 3600, function (dsf, dsw) { - s = dsw; - return dsf; - }); - } - return 0; - }); - - var secondsfrac = utils.modf(seconds, function (sf, s) { - seconds = s; - return sf + daysecondsfrac; - }); - utils.divmod(seconds, 24*3600, function (days, seconds) { - d += days; - s += seconds; - }); - // seconds isn't referenced again before redefinition - - microseconds += secondsfrac * 1e6; - utils.divmod(microseconds, 1000000, function (seconds, microseconds) { - utils.divmod(seconds, 24*3600, function (days, seconds) { - d += days; - s += seconds; - m += Math.round(microseconds); - }); - }); - - // Carrying still possible here? - - this.days = d; - this.seconds = s; - this.microseconds = m; - }, - __str__: function () { - var hh, mm, ss; - utils.divmod(this.seconds, 60, function (m, s) { - utils.divmod(m, 60, function (h, m) { - hh = h; - mm = m; - ss = s; - }); - }); - var s = _.str.sprintf("%d:%02d:%02d", hh, mm, ss); - if (this.days) { - s = _.str.sprintf("%d day%s, %s", - this.days, - (this.days != 1 && this.days != -1) ? 's' : '', - s); - } - if (this.microseconds) { - s = _.str.sprintf("%s.%06d", s, this.microseconds); - } - return py.str.fromJSON(s); - }, - __eq__: function (other) { - if (!py.PY_isInstance(other, datetime.timedelta)) { - return py.False; - } - - return (this.days === other.days - && this.seconds === other.seconds - && this.microseconds === other.microseconds) - ? py.True : py.False; - }, - __add__: function (other) { - if (!py.PY_isInstance(other, datetime.timedelta)) { - return py.NotImplemented; - } - return py.PY_call(datetime.timedelta, [ - py.float.fromJSON(this.days + other.days), - py.float.fromJSON(this.seconds + other.seconds), - py.float.fromJSON(this.microseconds + other.microseconds) - ]); - }, - __radd__: function (other) { return this.__add__(other); }, - __sub__: function (other) { - if (!py.PY_isInstance(other, datetime.timedelta)) { - return py.NotImplemented; - } - return py.PY_call(datetime.timedelta, [ - py.float.fromJSON(this.days - other.days), - py.float.fromJSON(this.seconds - other.seconds), - py.float.fromJSON(this.microseconds - other.microseconds) - ]); - }, - __rsub__: function (other) { - if (!py.PY_isInstance(other, datetime.timedelta)) { - return py.NotImplemented; - } - return this.__neg__().__add__(other); - }, - __neg__: function () { - return py.PY_call(datetime.timedelta, [ - py.float.fromJSON(-this.days), - py.float.fromJSON(-this.seconds), - py.float.fromJSON(-this.microseconds) - ]); - }, - __pos__: function () { return this; }, - __mul__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - var n = other.toJSON(); - return py.PY_call(datetime.timedelta, [ - py.float.fromJSON(this.days * n), - py.float.fromJSON(this.seconds * n), - py.float.fromJSON(this.microseconds * n) - ]); - }, - __rmul__: function (other) { return this.__mul__(other); }, - __div__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - var usec = ((this.days * 24 * 3600) + this.seconds) * 1000000 - + this.microseconds; - return py.PY_call( - datetime.timedelta, [ - zero, zero, py.float.fromJSON(usec / other.toJSON())]); - }, - __floordiv__: function (other) { return this.__div__(other); }, - total_seconds: function () { - return py.float.fromJSON( - this.days * 86400 - + this.seconds - + this.microseconds / 1000000); - }, - __nonzero__: function () { - return (!!this.days || !!this.seconds || !!this.microseconds) - ? py.True - : py.False; - } -}); - -datetime.datetime = py.type('datetime', null, { - __init__: function () { - var zero = py.float.fromJSON(0); - var args = py.PY_parseArgs(arguments, [ - 'year', 'month', 'day', - ['hour', zero], ['minute', zero], ['second', zero], - ['microsecond', zero], ['tzinfo', py.None] - ]); - for(var key in args) { - if (!args.hasOwnProperty(key)) { continue; } - this[key] = asJS(args[key]); - } - }, - replace: function () { - var args = py.PY_parseArgs(arguments, [ - ['year', py.None], ['month', py.None], ['day', py.None], - ['hour', py.None], ['minute', py.None], ['second', py.None], - ['microsecond', py.None] // FIXME: tzinfo, can't use None as valid input - ]); - var params = {}; - for(var key in args) { - if (!args.hasOwnProperty(key)) { continue; } - - var arg = args[key]; - params[key] = (arg === py.None ? this[key] : asJS(arg)); - } - return py.PY_call(datetime.datetime, params); - }, - strftime: function () { - var self = this; - var args = py.PY_parseArgs(arguments, 'format'); - return py.str.fromJSON(args.format.toJSON() - .replace(/%([A-Za-z])/g, function (m, c) { - switch (c) { - case 'Y': return _.str.sprintf('%04d', self.year); - case 'm': return _.str.sprintf('%02d', self.month); - case 'd': return _.str.sprintf('%02d', self.day); - case 'H': return _.str.sprintf('%02d', self.hour); - case 'M': return _.str.sprintf('%02d', self.minute); - case 'S': return _.str.sprintf('%02d', self.second); - } - throw new Error('ValueError: No known conversion for ' + m); - })); - }, - now: py.classmethod.fromJSON(function () { - var d = new Date(); - return py.PY_call(datetime.datetime, [ - d.getFullYear(), d.getMonth() + 1, d.getDate(), - d.getHours(), d.getMinutes(), d.getSeconds(), - d.getMilliseconds() * 1000]); - }), - today: py.classmethod.fromJSON(function () { - var dt_class = py.PY_getAttr(datetime, 'datetime'); - return py.PY_call(py.PY_getAttr(dt_class, 'now')); - }), - utcnow: py.classmethod.fromJSON(function () { - var d = new Date(); - return py.PY_call(datetime.datetime, - [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), - d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), - d.getUTCMilliseconds() * 1000]); - }), - combine: py.classmethod.fromJSON(function () { - var args = py.PY_parseArgs(arguments, 'date time'); - return py.PY_call(datetime.datetime, [ - py.PY_getAttr(args.date, 'year'), - py.PY_getAttr(args.date, 'month'), - py.PY_getAttr(args.date, 'day'), - py.PY_getAttr(args.time, 'hour'), - py.PY_getAttr(args.time, 'minute'), - py.PY_getAttr(args.time, 'second') - ]); - }), - toJSON: function () { - return new Date( - this.year, - this.month - 1, - this.day, - this.hour, - this.minute, - this.second, - this.microsecond / 1000); - }, -}); - -datetime.date = py.type('date', null, { - __init__: function () { - var args = py.PY_parseArgs(arguments, 'year month day'); - this.year = asJS(args.year); - this.month = asJS(args.month); - this.day = asJS(args.day); - }, - strftime: function () { - var self = this; - var args = py.PY_parseArgs(arguments, 'format'); - return py.str.fromJSON(args.format.toJSON() - .replace(/%([A-Za-z])/g, function (m, c) { - switch (c) { - case 'Y': return self.year; - case 'm': return _.str.sprintf('%02d', self.month); - case 'd': return _.str.sprintf('%02d', self.day); - } - throw new Error('ValueError: No known conversion for ' + m); - })); - }, - __eq__: function (other) { - return (this.year === other.year - && this.month === other.month - && this.day === other.day) - ? py.True : py.False; - }, - replace: function () { - var args = py.PY_parseArgs(arguments, [ - ['year', py.None], ['month', py.None], ['day', py.None] - ]); - var params = {}; - for(var key in args) { - if (!args.hasOwnProperty(key)) { continue; } - - var arg = args[key]; - params[key] = (arg === py.None ? this[key] : asJS(arg)); - } - return py.PY_call(datetime.date, params); - }, - __add__: function (other) { - if (!py.PY_isInstance(other, datetime.timedelta)) { - return py.NotImplemented; - } - var s = tmxxx(this.year, this.month, this.day + other.days); - return datetime.date.fromJSON(s.year, s.month, s.day); - }, - __radd__: function (other) { return this.__add__(other); }, - __sub__: function (other) { - if (py.PY_isInstance(other, datetime.timedelta)) { - return py.PY_add(this, py.PY_negative(other)); - } - if (py.PY_isInstance(other, datetime.date)) { - // FIXME: getattr and sub API methods - return py.PY_call(datetime.timedelta, [ - py.PY_subtract( - py.PY_call(py.PY_getAttr(this, 'toordinal')), - py.PY_call(py.PY_getAttr(other, 'toordinal'))) - ]); - } - return py.NotImplemented; - }, - toordinal: function () { - return py.float.fromJSON(ymd2ord(this.year, this.month, this.day)); - }, - weekday: function () { - return py.float.fromJSON((this.toordinal().toJSON()+6)%7); - }, - fromJSON: function (year, month, day) { - return py.PY_call(datetime.date, [year, month, day]); - }, - today: py.classmethod.fromJSON(function () { - var d = new Date (); - return py.PY_call(datetime.date, [ - d.getFullYear(), d.getMonth() + 1, d.getDate()]); - }), -}); -/** - Returns the current local date, which means the date on the client (which can be different - compared to the date of the server). - - @return {datetime.date} -*/ -function context_today () { - var d = new Date(); - return py.PY_call( - datetime.date, [d.getFullYear(), d.getMonth() + 1, d.getDate()]); -} - -datetime.time = py.type('time', null, { - __init__: function () { - var zero = py.float.fromJSON(0); - var args = py.PY_parseArgs(arguments, [ - ['hour', zero], ['minute', zero], ['second', zero], ['microsecond', zero], - ['tzinfo', py.None] - ]); - - for(var k in args) { - if (!args.hasOwnProperty(k)) { continue; } - this[k] = asJS(args[k]); - } - } -}); - -var time = py.PY_call(py.object); -time.strftime = py.PY_def.fromJSON(function () { - var args = py.PY_parseArgs(arguments, 'format'); - var dt_class = py.PY_getAttr(datetime, 'datetime'); - var d = py.PY_call(py.PY_getAttr(dt_class, 'utcnow')); - return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]); -}); - -var args = _.map(('year month day ' - + 'years months weeks days ' - + 'weekday leapdays yearday nlyearday').split(' '), function (arg) { - switch (arg) { - case 'years':case 'months':case 'days':case 'leapdays':case 'weeks': - return [arg, zero]; - case 'year':case 'month':case 'day':case 'weekday': - case 'yearday':case 'nlyearday': - return [arg, null]; - default: - throw new Error("Unknown relativedelta argument " + arg); - } -}); -args.unshift('*'); - -var _utils = { - monthrange: function (year, month) { - if (month < 1 || month > 12) { - throw new Error("Illegal month " + month); - } - - var day1 = this.weekday(year, month, 1); - var ndays = this.mdays[month] + (month == this.February && this.isleap(year)); - return [day1, ndays]; - }, - weekday: function (year, month, day) { - var date = py.PY_call(datetime.date, [year, month, day]); - return py.PY_call(py.PY_getAttr(date, 'weekday')); - }, - isleap: function (year) { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) - }, - mdays: [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - January: 1, - February: 2 -}; - -var relativedelta = py.type('relativedelta', null, { - __init__: function () { - this.ops = py.PY_parseArgs(arguments, args); - this.ops.days = py.float.fromJSON( - asJS(this.ops.days) + asJS(this.ops.weeks) * 7 - ); - - var yday = zero; - if (this.ops.nlyearday) { - yday = this.ops.nlyearday; - } else if (this.ops.yearday) { - yday = this.ops.yearday; - if (asJS(this.ops.yearday) > 59) { - this.ops.leapdays = py.float.fromJS(-1); - } - } - if (py.PY_isTrue(yday)) { - var ydayidx = [31, 59, 90, 120, 151, 181, 212, - 243, 273, 304, 334, 366]; - for(var idx=0; idx<ydayidx.length; ++idx) { - var ydays = ydayidx[idx]; - if (asJS(yday) <= ydays) { - this.ops.month = py.float.fromJSON(idx+1); - if (!idx) { - this.ops.day = yday; - } else { - this.ops.day = py.PY_subtract( - yday, - py.float.fromJSON(ydayidx[idx-1]) - ); - } - break; - } - } - if (idx === ydayidx.length) { - throw new Error("Invalid year day (" + asJS(yday) + ")"); - } - } - this._fix(); - }, - _fix: function () { - var months = asJS(this.ops.months); - if (Math.abs(months) > 11) { - var s = months > 0 ? 1 : -1; - var r = utils.divmod(months * s, 12); - this.ops.months = py.float.fromJSON(r.mod*s); - this.ops.years = py.float.fromJSON( - asJS(this.ops.years) + r.div*s); - } - this._has_time = 0; - }, - __add__: function (other) { - if (!py.PY_isInstance(other, datetime.date)) { - return py.NotImplemented; - } - // TODO: test this whole mess - var year = (asJS(this.ops.year) || asJS(other.year)) + asJS(this.ops.years); - var month = asJS(this.ops.month) || asJS(other.month); - var months; - if (months = asJS(this.ops.months)) { - if (Math.abs(months) < 1 || Math.abs(months) > 12) { - throw new Error("Can only use relative months between -12 and +12"); - } - month += months; - if (month > 12) { - year += 1; - month -= 12; - } - if (month < 1) { - year -= 1; - month += 12; - } - } - - var day = Math.min(_utils.monthrange(year, month)[1], - asJS(this.ops.day) || asJS(other.day)); - - var repl = { - year: py.float.fromJSON(year), - month: py.float.fromJSON(month), - day: py.float.fromJSON(day) - }; - - var days = asJS(this.ops.days); - if (py.PY_isTrue(this.ops.leapdays) && month > 2 && _utils.isleap(year)) { - days += asJS(this.ops.leapdays); - } - - var ret = py.PY_add( - py.PY_call(py.PY_getAttr(other, 'replace'), repl), - py.PY_call(datetime.timedelta, { - days: py.float.fromJSON(days) - }) - ); - - if (this.ops.weekday) { - // FIXME: only handles numeric weekdays, not decorated - var weekday = asJS(this.ops.weekday), nth = 1; - var jumpdays = (Math.abs(nth) - 1) * 7; - - var ret_weekday = asJS(py.PY_call(py.PY_getAttr(ret, 'weekday'))); - if (nth > 0) { - jumpdays += (7-ret_weekday+weekday) % 7; - } else { - jumpdays += (ret_weekday - weekday) % 7; - jumpdays *= -1; - } - ret = py.PY_add( - ret, - py.PY_call(datetime.timedelta, { - days: py.float.fromJSON(jumpdays) - }) - ); - } - - return ret; - }, - __radd__: function (other) { - return this.__add__(other); - }, - __rsub__: function (other) { - return this.__neg__().__radd__(other); - }, - __neg__: function () { - return py.PY_call(relativedelta, { - years: py.PY_negative(this.ops.years), - months: py.PY_negative(this.ops.months), - days: py.PY_negative(this.ops.days), - leapdays: this.ops.leapdays, - year: this.ops.year, - month: this.ops.month, - day: this.ops.day, - weekday: this.ops.weekday - }); - } -}); - -// recursively wraps JS objects passed into the context to attributedicts -// which jsonify back to JS objects -function wrap(value) { - if (value === null) { return py.None; } - - switch (typeof value) { - case 'undefined': throw new Error("No conversion for undefined"); - case 'boolean': return py.bool.fromJSON(value); - case 'number': return py.float.fromJSON(value); - case 'string': return py.str.fromJSON(value); - } - - switch(value.constructor) { - case Object: return wrapping_dict.fromJSON(value); - case Array: return wrapping_list.fromJSON(value); - } - - throw new Error("ValueError: unable to wrap " + value); -} - -var wrapping_dict = py.type('wrapping_dict', null, { - __init__: function () { - this._store = {}; - }, - __getitem__: function (key) { - var k = key.toJSON(); - if (!(k in this._store)) { - throw new Error("KeyError: '" + k + "'"); - } - return wrap(this._store[k]); - }, - __getattr__: function (key) { - return this.__getitem__(py.str.fromJSON(key)); - }, - __len__: function () { - return Object.keys(this._store).length - }, - __nonzero__: function () { - return py.PY_size(this) > 0 ? py.True : py.False; - }, - get: function () { - var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]); - - if (!(args.k.toJSON() in this._store)) { return args.d; } - return this.__getitem__(args.k); - }, - fromJSON: function (d) { - var instance = py.PY_call(wrapping_dict); - instance._store = d; - return instance; - }, - toJSON: function () { - return this._store; - }, -}); - -var wrapping_list = py.type('wrapping_list', null, { - __init__: function () { - this._store = []; - }, - __getitem__: function (index) { - return wrap(this._store[index.toJSON()]); - }, - __len__: function () { - return this._store.length; - }, - __nonzero__: function () { - return py.PY_size(this) > 0 ? py.True : py.False; - }, - fromJSON: function (ar) { - var instance = py.PY_call(wrapping_list); - instance._store = ar; - return instance; - }, - toJSON: function () { - return this._store; - }, -}); - -function wrap_context (context) { - for (var k in context) { - if (!context.hasOwnProperty(k)) { continue; } - var val = context[k]; - - if (val === null) { continue; } - if (val.constructor === Array) { - context[k] = wrapping_list.fromJSON(val); - } else if (val.constructor === Object - && !py.PY_isInstance(val, py.object)) { - context[k] = wrapping_dict.fromJSON(val); - } - } - return context; -} - -function eval_contexts (contexts, evaluation_context) { - evaluation_context = _.extend(pycontext(), evaluation_context || {}); - return _(contexts).reduce(function (result_context, ctx) { - // __eval_context evaluations can lead to some of `contexts`'s - // values being null, skip them as well as empty contexts - if (_.isEmpty(ctx)) { return result_context; } - if (_.isString(ctx)) { - // wrap raw strings in context - ctx = { __ref: 'context', __debug: ctx }; - } - var evaluated = ctx; - switch(ctx.__ref) { - case 'context': - evaluation_context.context = evaluation_context; - evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context)); - break; - case 'compound_context': - var eval_context = eval_contexts([ctx.__eval_context]); - evaluated = eval_contexts( - ctx.__contexts, _.extend({}, evaluation_context, eval_context)); - break; - } - // add newly evaluated context to evaluation context for following - // siblings - _.extend(evaluation_context, evaluated); - return _.extend(result_context, evaluated); - }, {}); -} - -function eval_domains (domains, evaluation_context) { - evaluation_context = _.extend(pycontext(), evaluation_context || {}); - var result_domain = []; - // Normalize only if the first domain is the array ["|"] or ["!"] - var need_normalization = ( - domains && - domains.length > 0 && - domains[0].length === 1 && - (domains[0][0] === "|" || domains[0][0] === "!") - ); - _(domains).each(function (domain) { - if (_.isString(domain)) { - // wrap raw strings in domain - domain = { __ref: 'domain', __debug: domain }; - } - var domain_array_to_combine; - switch(domain.__ref) { - case 'domain': - evaluation_context.context = evaluation_context; - domain_array_to_combine = py.eval(domain.__debug, wrap_context(evaluation_context)); - break; - case 'compound_domain': - var eval_context = eval_contexts([domain.__eval_context]); - domain_array_to_combine = eval_domains( - domain.__domains, _.extend({}, evaluation_context, eval_context) - ); - break; - default: - domain_array_to_combine = domain; - } - if (need_normalization) { - domain_array_to_combine = get_normalized_domain(domain_array_to_combine); - } - result_domain.push.apply(result_domain, domain_array_to_combine); - }); - return result_domain; -} - -/** - * Returns a normalized copy of the given domain array. Normalization is - * is making the implicit "&" at the start of the domain explicit, e.g. - * [A, B, C] would become ["&", "&", A, B, C]. - * - * @param {Array} domain_array - * @returns {Array} normalized copy of the given array - */ -function get_normalized_domain(domain_array) { - var expected = 1; // Holds the number of expected domain expressions - _.each(domain_array, function (item) { - if (item === "&" || item === "|") { - expected++; - } else if (item !== "!") { - expected--; - } - }); - var new_explicit_ands = _.times(-expected, _.constant("&")); - return new_explicit_ands.concat(domain_array); -} - -function eval_groupbys (contexts, evaluation_context) { - evaluation_context = _.extend(pycontext(), evaluation_context || {}); - var result_group = []; - _(contexts).each(function (ctx) { - if (_.isString(ctx)) { - // wrap raw strings in context - ctx = { __ref: 'context', __debug: ctx }; - } - var group; - var evaluated = ctx; - switch(ctx.__ref) { - case 'context': - evaluation_context.context = evaluation_context; - evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context)); - break; - case 'compound_context': - var eval_context = eval_contexts([ctx.__eval_context]); - evaluated = eval_contexts( - ctx.__contexts, _.extend({}, evaluation_context, eval_context)); - break; - } - group = evaluated.group_by; - if (!group) { return; } - if (typeof group === 'string') { - result_group.push(group); - } else if (group instanceof Array) { - result_group.push.apply(result_group, group); - } else { - throw new Error('Got invalid groupby {{' - + JSON.stringify(group) + '}}'); - } - _.extend(evaluation_context, evaluated); - }); - return result_group; -} - - -function pycontext () { - return _.extend({ - datetime: datetime, - context_today: context_today, - time: time, - relativedelta: relativedelta, - current_date: py.PY_call( - time.strftime, [py.str.fromJSON('%Y-%m-%d')]), - }, session.user_context); -} - -/** - * @param {String} type "domains", "contexts" or "groupbys" - * @param {Array} object domains or contexts to evaluate - * @param {Object} [context] evaluation context - */ -function pyeval (type, object, context, options) { - options = options || {}; - context = _.extend(pycontext(), context || {}); - - //noinspection FallthroughInSwitchStatementJS - switch(type) { - case 'context': - case 'contexts': - if (type === 'context') - object = [object]; - return eval_contexts((options.no_user_context ? [] : [session.user_context]).concat(object), context); - case 'domain': - case 'domains': - if (type === 'domain') - object = [object]; - return eval_domains(object, context); - case 'groupbys': - return eval_groupbys(object, context); - } - throw new Error("Unknow evaluation type " + type); -} - -function eval_arg (arg) { - if (typeof arg !== 'object' || !arg.__ref) { return arg; } - switch(arg.__ref) { - case 'domain': case 'compound_domain': - return pyeval('domains', [arg]); - case 'context': case 'compound_context': - return pyeval('contexts', [arg]); - default: - throw new Error(_t("Unknown nonliteral type ") + ' ' + arg.__ref); - } -} - -/** - * If args or kwargs are unevaluated contexts or domains (compound or not), - * evaluated them in-place. - * - * Potentially mutates both parameters. - * - * @param args - * @param kwargs - */ -function ensure_evaluated (args, kwargs) { - for (var i=0; i<args.length; ++i) { - args[i] = eval_arg(args[i]); - } - for (var k in kwargs) { - if (!kwargs.hasOwnProperty(k)) { continue; } - kwargs[k] = eval_arg(kwargs[k]); - } -} - -function eval_domains_and_contexts (source) { - return new $.Deferred(function (d) {setTimeout(function () { - var result; - try { - result = sync_eval_domains_and_contexts(source); - } - catch (e) { - result = { error: { - code: 400, - message: _t("Evaluation Error"), - data: { - type: 'local_exception', - debug: _.str.sprintf( - _t("Local evaluation failure\n%s\n\n%s"), - e.message, JSON.stringify(source)) - } - }}; - } - d.resolve(result); - }, 0); }); -} - -function sync_eval_domains_and_contexts (source) { - var contexts = ([session.user_context] || []).concat(source.contexts); - // see Session.eval_context in Python - return { - context: pyeval('contexts', contexts), - domain: pyeval('domains', source.domains), - group_by: pyeval('groupbys', source.group_by_seq || []) - }; -} - -function py_eval (expr, context) { - return py.eval(expr, _.extend({}, context || {}, {"true": true, "false": false, "null": null})); -} - - -return { - context: pycontext, - ensure_evaluated: ensure_evaluated, - eval: pyeval, - eval_domains_and_contexts: eval_domains_and_contexts, - sync_eval_domains_and_contexts: sync_eval_domains_and_contexts, - py_eval: py_eval, -}; - -}); diff --git a/addons/web/static/src/js/framework/qweb.js b/addons/web/static/src/js/framework/qweb.js deleted file mode 100644 index 50742075e05..00000000000 --- a/addons/web/static/src/js/framework/qweb.js +++ /dev/null @@ -1,51 +0,0 @@ -odoo.define('web.QWeb', function (require) { -"use strict"; - -var translation = require('web.translation'); - -var _t = translation._t; - -function QWeb(debug, default_dict) { - var qweb = new QWeb2.Engine(); - qweb.default_dict = _.extend({}, default_dict || {}, { - '_' : _, - 'JSON': JSON, - '_t' : translation._t, - '__debug__': debug, - 'moment': function(date) { return new moment(date); }, - 'csrf_token': odoo.csrf_token, - }); - qweb.debug = debug; - qweb.preprocess_node = preprocess_node; - return qweb; -} - -function preprocess_node() { - // Note that 'this' is the Qweb Node - switch (this.node.nodeType) { - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - // Text and CDATAs - var translation = this.node.parentNode.attributes['t-translation']; - if (translation && translation.value === 'off') { - return; - } - var match = /^(\s*)([\s\S]+?)(\s*)$/.exec(this.node.data); - if (match) { - this.node.data = match[1] + _t(match[2]) + match[3]; - } - break; - case Node.ELEMENT_NODE: - // Element - var attr, attrs = ['label', 'title', 'alt', 'placeholder']; - while ((attr = attrs.pop())) { - if (this.attributes[attr]) { - this.attributes[attr] = _t(this.attributes[attr]); - } - } - } -} - -return QWeb; - -}); diff --git a/addons/web/static/src/js/framework/registry.js b/addons/web/static/src/js/framework/registry.js deleted file mode 100644 index b8bc1dbadfe..00000000000 --- a/addons/web/static/src/js/framework/registry.js +++ /dev/null @@ -1,54 +0,0 @@ -odoo.define('web.Registry', function (require) { -"use strict"; - -var Class = require('web.Class'); - -var Registry = Class.extend({ - init: function (mapping) { - this.map = Object.create(mapping || null); - }, - get: function (key) { - return this.map[key]; - }, - contains: function (key) { - return (key in this.map); - }, - /** - * Tries a number of keys, and returns the first object matching one of - * the keys. - * - * @param {Array} keys a sequence of keys to fetch the object for - * @returns {Class} the first class found matching an object - */ - get_any: function (keys) { - for (var i=0; i<keys.length; i++) { - if (keys[i] in this.map) { - return this.map[keys[i]]; - } - } - return null; - }, - add: function (key, value) { - this.map[key] = value; - return this; - }, - /** - * Creates and returns a copy of the current mapping, with the provided - * mapping argument added in (replacing existing keys if needed) - * - * Parent and child remain linked, a new key in the parent (which is not - * overwritten by the child) will appear in the child. - * - * @param {Object} [mapping={}] a mapping of keys to object-paths - */ - extend: function (mapping) { - var child = new Registry(this.map); - _.extend(child.map, mapping); - return child; - }, -}); - -return Registry; - -}); - diff --git a/addons/web/static/src/js/framework/session.js b/addons/web/static/src/js/framework/session.js deleted file mode 100644 index 516436af93c..00000000000 --- a/addons/web/static/src/js/framework/session.js +++ /dev/null @@ -1,411 +0,0 @@ -odoo.define('web.Session', function (require) { -"use strict"; - -var ajax = require('web.ajax'); -var core = require('web.core'); -var utils = require('web.utils'); - -var _t = core._t; -var qweb = core.qweb; -var mixins = core.mixins; - -// To do: refactor session. Session accomplishes several concerns (rpc, -// configuration, currencies (wtf?), user permissions...). They should be -// clarified and separated. - -var Session = core.Class.extend(mixins.EventDispatcherMixin, { - /** - - @param parent The parent of the newly created object. - or `null` if the server to contact is the origin server. - @param {Dict} options A dictionary that can contain the following options: - - * "override_session": Default to false. If true, the current session object will - not try to re-use a previously created session id stored in a cookie. - * "session_id": Default to null. If specified, the specified session_id will be used - by this session object. Specifying this option automatically implies that the option - "override_session" is set to true. - */ - init: function(parent, origin, options) { - mixins.EventDispatcherMixin.init.call(this, parent); - options = options || {}; - this.module_list = (options.modules && options.modules.slice()) || (window.odoo._modules && window.odoo._modules.slice()) || []; - this.server = null; - this.session_id = options.session_id || null; - this.override_session = options.override_session || !!options.session_id || false; - this.avoid_recursion = false; - this.use_cors = options.use_cors || false; - this.setup(origin); - var debug_param = $.deparam($.param.querystring()).debug; - this.debug = (debug_param !== undefined ? debug_param || 1 : false); - - // for historic reasons, the session requires a name to properly work - // (see the methods get_cookie and set_cookie). We should perhaps - // remove it totally (but need to make sure the cookies are properly set) - this.name = "instance0"; - // TODO: session store in cookie should be optional - this.qweb_mutex = new utils.Mutex(); - this.currencies = {}; - this._groups_def = {}; - }, - setup: function(origin, options) { - // must be able to customize server - var window_origin = location.protocol + "//" + location.host; - origin = origin ? origin.replace( /\/+$/, '') : window_origin; - if (!_.isUndefined(this.origin) && this.origin !== origin) - throw new Error('Session already bound to ' + this.origin); - else - this.origin = origin; - this.prefix = this.origin; - this.server = this.origin; // keep chs happy - this.origin_server = this.origin === window_origin; - options = options || {}; - if ('use_cors' in options) { - this.use_cors = options.use_cors; - } - }, - /** - * Setup a session - */ - session_bind: function(origin) { - var self = this; - this.setup(origin); - qweb.default_dict._s = this.origin; - this.uid = null; - this.username = null; - this.user_context= {}; - this.db = null; - this.module_loaded = {}; - _(this.module_list).each(function (mod) { - self.module_loaded[mod] = true; - }); - this.active_id = null; - return this.session_init(); - }, - /** - * Init a session, reloads from cookie, if it exists - */ - session_init: function () { - var def = this.session_reload(); - if (this.is_frontend) return def; - - var self = this; - return def.then(function() { - var modules = self.module_list.join(','); - var deferred = self.load_qweb(modules); - if(self.session_is_valid()) { - return deferred.then(function() { return self.load_modules(); }); - } - return $.when( - deferred, - self.rpc('/web/webclient/bootstrap_translations', {mods: self.module_list}).then(function(trans) { - _t.database.set_bundle(trans); - }) - ); - }); - }, - session_is_valid: function() { - var db = $.deparam.querystring().db; - if (db && this.db !== db) { - return false; - } - return !!this.uid; - }, - /** - * The session is validated by restoration of a previous session - */ - session_authenticate: function() { - var self = this; - return $.when(this._session_authenticate.apply(this, arguments)).then(function() { - return self.load_modules(); - }); - }, - /** - * The session is validated either by login or by restoration of a previous session - */ - _session_authenticate: function(db, login, password) { - var self = this; - var params = {db: db, login: login, password: password}; - return this.rpc("/web/session/authenticate", params).then(function(result) { - if (!result.uid) { - return $.Deferred().reject(); - } - delete result.session_id; - _.extend(self, result); - }); - }, - session_logout: function() { - $.bbq.removeState(); - return this.rpc("/web/session/destroy", {}); - }, - user_has_group: function(group) { - if (!this.uid) { - return $.when(false); - } - var def = this._groups_def[group]; - if (!def) { - def = this._groups_def[group] = this.rpc('/web/dataset/call_kw/res.users/has_group', { - "model": "res.users", - "method": "has_group", - "args": [group], - "kwargs": {} - }); - } - return def; - }, - get_cookie: function (name) { - if (!this.name) { return null; } - var nameEQ = this.name + '|' + name + '='; - var cookies = document.cookie.split(';'); - for(var i=0; i<cookies.length; ++i) { - var cookie = cookies[i].replace(/^\s*/, ''); - if(cookie.indexOf(nameEQ) === 0) { - try { - return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length))); - } catch(err) { - // wrong cookie, delete it - this.set_cookie(name, '', -1); - } - } - } - return null; - }, - /** - * Create a new cookie with the provided name and value - * - * @private - * @param name the cookie's name - * @param value the cookie's value - * @param ttl the cookie's time to live, 1 year by default, set to -1 to delete - */ - set_cookie: function (name, value, ttl) { - if (!this.name) { return; } - ttl = ttl || 24*60*60*365; - utils.set_cookie(this.name + '|' + name, value, ttl); - }, - /** - * Load additional web addons of that instance and init them - * - */ - load_modules: function() { - var self = this; - var modules = odoo._modules; - var all_modules = _.uniq(self.module_list.concat(modules)); - var to_load = _.difference(modules, self.module_list).join(','); - this.module_list = all_modules; - - var loaded = $.when(self.load_translations()); - var locale = "/web/webclient/locale/" + self.user_context.lang || 'en_US'; - var file_list = [ locale ]; - if(to_load.length) { - loaded = $.when( - loaded, - self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)), - self.load_qweb(to_load), - self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) { - file_list = file_list.concat(files); - }) - ); - } - return loaded.then(function () { - return self.load_js(file_list); - }).done(function() { - self.on_modules_loaded(); - self.trigger('module_loaded'); - }); - }, - load_translations: function() { - return _t.database.load_translations(this, this.module_list, this.user_context.lang); - }, - load_css: function (files) { - var self = this; - _.each(files, function (file) { - ajax.loadCSS(self.url(file, null)); - }); - }, - load_js: function(files) { - var self = this; - var d = $.Deferred(); - if (files.length !== 0) { - var file = files.shift(); - var url = self.url(file, null); - ajax.loadJS(url).done(d.resolve); - } else { - d.resolve(); - } - return d; - }, - load_qweb: function(mods) { - this.qweb_mutex.exec(function () { - return $.get('/web/webclient/qweb?mods=' + mods).then(function (doc) { - if (!doc) { return; } - qweb.add_template(doc); - }); - }); - return this.qweb_mutex.def; - }, - on_modules_loaded: function() { - var openerp = window.openerp; - for(var j=0; j<this.module_list.length; j++) { - var mod = this.module_list[j]; - if(this.module_loaded[mod]) - continue; - openerp[mod] = {}; - // init module mod - var fct = openerp._openerp[mod]; - if(typeof(fct) === "function") { - openerp._openerp[mod] = {}; - for (var k in fct) { - openerp._openerp[mod][k] = fct[k]; - } - fct(openerp, openerp._openerp[mod]); - } - this.module_loaded[mod] = true; - } - }, - get_currency: function(currency_id) { - return this.currencies[currency_id]; - }, - get_file: function (options) { - if (this.override_session){ - options.data.session_id = this.session_id; - } - options.session = this; - ajax.get_file(options); - }, - /** - * (re)loads the content of a session: db name, username, user id, session - * context and status of the support contract - * - * @returns {$.Deferred} deferred indicating the session is done reloading - */ - session_reload: function () { - var result = _.extend({}, window.odoo.session_info); - delete result.session_id; - _.extend(this, result); - return $.when(); - }, - check_session_id: function() { - var self = this; - if (this.avoid_recursion) - return $.when(); - if (this.session_id) - return $.when(); // we already have the session id - if (!this.use_cors && (this.override_session || ! this.origin_server)) { - // If we don't use the origin server we consider we should always create a new session. - // Even if some browsers could support cookies when using jsonp that behavior is - // not consistent and the browser creators are tending to removing that feature. - this.avoid_recursion = true; - return this.rpc("/gen_session_id", {}).then(function(result) { - self.session_id = result; - }).always(function() { - self.avoid_recursion = false; - }); - } else { - // normal use case, just use the cookie - self.session_id = utils.get_cookie("session_id"); - return $.when(); - } - }, - /** - * Executes an RPC call, registering the provided callbacks. - * - * Registers a default error callback if none is provided, and handles - * setting the correct session id and session context in the parameter - * objects - * - * @param {String} url RPC endpoint - * @param {Object} params call parameters - * @param {Object} options additional options for rpc call - * @param {Function} success_callback function to execute on RPC call success - * @param {Function} error_callback function to execute on RPC call failure - * @returns {jQuery.Deferred} jquery-provided ajax deferred - */ - rpc: function(url, params, options) { - var self = this; - options = _.clone(options || {}); - var shadow = options.shadow || false; - options.headers = _.extend({}, options.headers) - if (odoo.debug) { - options.headers["X-Debug-Mode"] = $.deparam($.param.querystring()).debug; - } - - delete options.shadow; - - return self.check_session_id().then(function() { - // TODO: remove - if (! _.isString(url)) { - _.extend(options, url); - url = url.url; - } - // TODO correct handling of timeouts - if (! shadow) - self.trigger('request'); - var fct; - if (self.origin_server) { - fct = ajax.jsonRpc; - if (self.override_session) { - options.headers["X-Openerp-Session-Id"] = self.session_id || ''; - } - } else if (self.use_cors) { - fct = ajax.jsonRpc; - url = self.url(url, null); - options.session_id = self.session_id || ''; - if (self.override_session) { - options.headers["X-Openerp-Session-Id"] = self.session_id || ''; - } - } else { - fct = ajax.jsonpRpc; - url = self.url(url, null); - options.session_id = self.session_id || ''; - } - var p = fct(url, "call", params, options); - p = p.then(function (result) { - if (! shadow) - self.trigger('response'); - return result; - }, function(type, error, textStatus, errorThrown) { - if (type === "server") { - if (! shadow) - self.trigger('response'); - if (error.code === 100) { - self.uid = false; - } - return $.Deferred().reject(error, $.Event()); - } else { - if (! shadow) - self.trigger('response_failed'); - var nerror = { - code: -32098, - message: "XmlHttpRequestError " + errorThrown, - data: {type: "xhr"+textStatus, debug: error.responseText, objects: [error, errorThrown] } - }; - return $.Deferred().reject(nerror, $.Event()); - } - }); - return p.fail(function() { // Allow deferred user to disable rpc_error call in fail - p.fail(function(error, event) { - if (!event.isDefaultPrevented()) { - self.trigger('error', error, event); - } - }); - }); - }); - }, - url: function(path, params) { - params = _.extend(params || {}); - if (this.override_session || (! this.origin_server)) - params.session_id = this.session_id; - var qs = $.param(params); - if (qs.length > 0) - qs = "?" + qs; - var prefix = _.any(['http://', 'https://', '//'], function(el) { - return path.length >= el.length && path.slice(0, el.length) === el; - }) ? '' : this.prefix; - return prefix + path + qs; - }, -}); - -return Session; - -}); diff --git a/addons/web/static/src/js/framework/time.js b/addons/web/static/src/js/framework/time.js deleted file mode 100644 index af13aa5a3b1..00000000000 --- a/addons/web/static/src/js/framework/time.js +++ /dev/null @@ -1,299 +0,0 @@ -odoo.define('web.time', function (require) { -"use strict"; - -var translation = require('web.translation'); -var utils = require('web.utils'); - -var lpad = utils.lpad; -var rpad = utils.rpad; -var _t = translation._t; - -/** - * Replacer function for JSON.stringify, serializes Date objects to UTC - * datetime in the OpenERP Server format. - * - * However, if a serialized value has a toJSON method that method is called - * *before* the replacer is invoked. Date#toJSON exists, and thus the value - * passed to the replacer is a string, the original Date has to be fetched - * on the parent object (which is provided as the replacer's context). - * - * @param {String} k - * @param {Object} v - * @returns {Object} - */ -function date_to_utc (k, v) { - var value = this[k]; - if (!(value instanceof Date)) { return v; } - - return datetime_to_str(value); -} - -/** - * Converts a string to a Date javascript object using OpenERP's - * datetime string format (exemple: '2011-12-01 15:12:35.832'). - * - * The time zone is assumed to be UTC (standard for OpenERP 6.1) - * and will be converted to the browser's time zone. - * - * @param {String} str A string representing a datetime. - * @returns {Date} - */ -function str_to_datetime (str) { - if(!str) { - return str; - } - var regex = /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d(?:\.(\d+))?)$/; - var res = regex.exec(str); - if ( !res ) { - throw new Error("'" + str + "' is not a valid datetime"); - } - var tmp = new Date(2000,0,1); - tmp.setUTCMonth(1970); - tmp.setUTCMonth(0); - tmp.setUTCDate(1); - tmp.setUTCFullYear(parseFloat(res[1])); - tmp.setUTCMonth(parseFloat(res[2]) - 1); - tmp.setUTCDate(parseFloat(res[3])); - tmp.setUTCHours(parseFloat(res[4])); - tmp.setUTCMinutes(parseFloat(res[5])); - tmp.setUTCSeconds(parseFloat(res[6])); - tmp.setUTCSeconds(parseFloat(res[6])); - tmp.setUTCMilliseconds(parseFloat(utils.rpad((res[7] || "").slice(0, 3), 3))); - return tmp; -} - -/** - * Converts a string to a Date javascript object using OpenERP's - * date string format (exemple: '2011-12-01'). - * - * As a date is not subject to time zones, we assume it should be - * represented as a Date javascript object at 00:00:00 in the - * time zone of the browser. - * - * @param {String} str A string representing a date. - * @returns {Date} - */ -function str_to_date (str) { - if(!str) { - return str; - } - var regex = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; - var res = regex.exec(str); - if ( !res ) { - throw new Error("'" + str + "' is not a valid date"); - } - var tmp = new Date(2000,0,1); - tmp.setFullYear(parseFloat(res[1])); - tmp.setMonth(parseFloat(res[2]) - 1); - tmp.setDate(parseFloat(res[3])); - tmp.setHours(0); - tmp.setMinutes(0); - tmp.setSeconds(0); - return tmp; -} - -/** - * Converts a string to a Date javascript object using OpenERP's - * time string format (exemple: '15:12:35'). - * - * The OpenERP times are supposed to always be naive times. We assume it is - * represented using a javascript Date with a date 1 of January 1970 and a - * time corresponding to the meant time in the browser's time zone. - * - * @param {String} str A string representing a time. - * @returns {Date} - */ -function str_to_time (str) { - if(!str) { - return str; - } - var regex = /^(\d\d):(\d\d):(\d\d(?:\.(\d+))?)$/; - var res = regex.exec(str); - if ( !res ) { - throw new Error("'" + str + "' is not a valid time"); - } - var tmp = new Date(); - tmp.setFullYear(1970); - tmp.setMonth(0); - tmp.setDate(1); - tmp.setHours(parseFloat(res[1])); - tmp.setMinutes(parseFloat(res[2])); - tmp.setSeconds(parseFloat(res[3])); - tmp.setMilliseconds(parseFloat(rpad((res[4] || "").slice(0, 3), 3))); - return tmp; -} - -/** - * Converts a Date javascript object to a string using OpenERP's - * datetime string format (exemple: '2011-12-01 15:12:35'). - * - * The time zone of the Date object is assumed to be the one of the - * browser and it will be converted to UTC (standard for OpenERP 6.1). - * - * @param {Date} obj - * @returns {String} A string representing a datetime. - */ -function datetime_to_str (obj) { - if (!obj) { - return false; - } - return lpad(obj.getUTCFullYear(),4) + "-" + lpad(obj.getUTCMonth() + 1,2) + "-" - + lpad(obj.getUTCDate(),2) + " " + lpad(obj.getUTCHours(),2) + ":" - + lpad(obj.getUTCMinutes(),2) + ":" + lpad(obj.getUTCSeconds(),2); -} - -/** - * Converts a Date javascript object to a string using OpenERP's - * date string format (exemple: '2011-12-01'). - * - * As a date is not subject to time zones, we assume it should be - * represented as a Date javascript object at 00:00:00 in the - * time zone of the browser. - * - * @param {Date} obj - * @returns {String} A string representing a date. - */ -function date_to_str (obj) { - if (!obj) { - return false; - } - return lpad(obj.getFullYear(),4) + "-" + lpad(obj.getMonth() + 1,2) + "-" - + lpad(obj.getDate(),2); -} - -/** - * Converts a Date javascript object to a string using OpenERP's - * time string format (exemple: '15:12:35'). - * - * The OpenERP times are supposed to always be naive times. We assume it is - * represented using a javascript Date with a date 1 of January 1970 and a - * time corresponding to the meant time in the browser's time zone. - * - * @param {Date} obj - * @returns {String} A string representing a time. - */ -function time_to_str (obj) { - if (!obj) { - return false; - } - return lpad(obj.getHours(),2) + ":" + lpad(obj.getMinutes(),2) + ":" - + lpad(obj.getSeconds(),2); -} - -function auto_str_to_date (value) { - try { - return str_to_datetime(value); - } catch(e) {} - try { - return str_to_date(value); - } catch(e) {} - try { - return str_to_time(value); - } catch(e) {} - throw new Error(_.str.sprintf(_t("'%s' is not a correct date, datetime nor time"), value)); -} - -function auto_date_to_str (value, type) { - switch(type) { - case 'datetime': - return datetime_to_str(value); - case 'date': - return date_to_str(value); - case 'time': - return time_to_str(value); - default: - throw new Error(_.str.sprintf(_t("'%s' is not convertible to date, datetime nor time"), type)); - } -} - -/** - * Convert Python strftime to escaped moment.js format. - * - * @param {String} value original format - */ -function strftime_to_moment_format (value) { - if (_normalize_format_cache[value] === undefined) { - var isletter = /[a-zA-Z]/, - output = [], - inToken = false; - - for (var index=0; index < value.length; ++index) { - var character = value[index]; - if (character === '%' && !inToken) { - inToken = true; - continue; - } - if (isletter.test(character)) { - if (inToken && normalize_format_table[character] !== undefined) { - character = normalize_format_table[character]; - } else { - character = '[' + character + ']'; // moment.js escape - } - } - output.push(character); - inToken = false; - } - _normalize_format_cache[value] = output.join(''); - } - return _normalize_format_cache[value]; -} - -/** - * Convert moment.js format to python strftime - * - * @param {String} value original format - */ -function moment_to_strftime_format(value) { - var regex = /(MMMM|DDDD|dddd|YYYY|MMM|ddd|mm|ss|ww|WW|MM|YY|hh|HH|DD|A|d)/g; - return value.replace(regex, function(val){ - return '%'+inverse_normalize_format_table[val]; - }); -} - -var _normalize_format_cache = {}; -var normalize_format_table = { - // Python strftime to moment.js conversion table - // See openerp/addons/base/res/res_lang_view.xml - // for details about supported directives - 'a': 'ddd', - 'A': 'dddd', - 'b': 'MMM', - 'B': 'MMMM', - 'd': 'DD', - 'H': 'HH', - 'I': 'hh', - 'j': 'DDDD', - 'm': 'MM', - 'M': 'mm', - 'p': 'A', - 'S': 'ss', - 'U': 'ww', - 'W': 'WW', - 'w': 'd', - 'y': 'YY', - 'Y': 'YYYY', - // unsupported directives - 'c': 'ddd MMM D HH:mm:ss YYYY', - 'x': 'MM/DD/YY', - 'X': 'HH:mm:ss' -}; -var inverse_normalize_format_table = _.invert(normalize_format_table); - - - -return { - date_to_utc: date_to_utc, - str_to_datetime: str_to_datetime, - str_to_date: str_to_date, - str_to_time: str_to_time, - datetime_to_str: datetime_to_str, - date_to_str: date_to_str, - time_to_str: time_to_str, - auto_str_to_date: auto_str_to_date, - auto_date_to_str: auto_date_to_str, - strftime_to_moment_format: strftime_to_moment_format, - moment_to_strftime_format: moment_to_strftime_format, -}; - -}); - diff --git a/addons/web/static/src/js/framework/translation.js b/addons/web/static/src/js/framework/translation.js deleted file mode 100644 index f2bad0db4ee..00000000000 --- a/addons/web/static/src/js/framework/translation.js +++ /dev/null @@ -1,117 +0,0 @@ - -odoo.define('web.translation', function (require) { -"use strict"; - -var Class = require('web.Class'); - -var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase# */{ - init: function() { - this.db = {}; - this.multi_lang = false - this.parameters = {"direction": 'ltr', - "date_format": '%m/%d/%Y', - "time_format": '%H:%M:%S', - "grouping": [], - "decimal_point": ".", - "thousands_sep": ","}; - }, - set_bundle: function(translation_bundle) { - var self = this; - this.multi_lang = translation_bundle.multi_lang - var modules = _.keys(translation_bundle.modules); - modules.sort(); - if (_.include(modules, "web")) { - modules = ["web"].concat(_.without(modules, "web")); - } - _.each(modules, function(name) { - self.add_module_translation(translation_bundle.modules[name]); - }); - if (translation_bundle.lang_parameters) { - this.parameters = translation_bundle.lang_parameters; - this.parameters.grouping = JSON.parse(this.parameters.grouping); - } - }, - add_module_translation: function(mod) { - var self = this; - _.each(mod.messages, function(message) { - self.db[message.id] = message.string; - }); - }, - build_translation_function: function() { - var self = this; - var fcnt = function(str) { - var tmp = self.get(str); - return tmp === undefined ? str : tmp; - }; - fcnt.database = this; - return fcnt; - }, - get: function(key) { - return this.db[key]; - }, - /** - Loads the translations from an OpenERP server. - - @param {openerp.Session} session The session object to contact the server. - @param {Array} [modules] The list of modules to load the translation. If not specified, - it will default to all the modules installed in the current database. - @param {Object} [lang] lang The language. If not specified it will default to the language - of the current user. - @returns {jQuery.Deferred} - */ - load_translations: function(session, modules, lang) { - var self = this; - return session.rpc('/web/webclient/translations', { - "mods": modules || null, - "lang": lang || null - }).done(function(trans) { - self.set_bundle(trans); - }); - } -}); - -var _t = new TranslationDataBase().build_translation_function(); -/** - * Lazy translation function, only performs the translation when actually - * printed (e.g. inserted into a template) - * - * Useful when defining translatable strings in code evaluated before the - * translation database is loaded, as class attributes or at the top-level of - * an OpenERP Web module - * - * @param {String} s string to translate - * @returns {Object} lazy translation object - */ -var _lt = function (s) { - return {toString: function () { return _t(s); }}; -}; - -/** Setup jQuery timeago */ -/* - * Strings in timeago are "composed" with prefixes, words and suffixes. This - * makes their detection by our translating system impossible. Use all literal - * strings we're using with a translation mark here so the extractor can do its - * job. - */ -{ - _t('less than a minute ago'); - _t('about a minute ago'); - _t('%d minutes ago'); - _t('about an hour ago'); - _t('%d hours ago'); - _t('a day ago'); - _t('%d days ago'); - _t('about a month ago'); - _t('%d months ago'); - _t('about a year ago'); - _t('%d years ago'); -} - - -return { - _t: _t, - _lt: _lt, - TranslationDataBase: TranslationDataBase, -}; - -}); diff --git a/addons/web/static/src/js/framework/utils.js b/addons/web/static/src/js/framework/utils.js deleted file mode 100644 index 6b0a2ddbcc0..00000000000 --- a/addons/web/static/src/js/framework/utils.js +++ /dev/null @@ -1,566 +0,0 @@ -odoo.define('web.utils', 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.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); - } 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 ""; -} - -/** - * Create a cookie - * @param {String} name : the name of the cookie - * @param {String} value : the value stored in the cookie - * @param {Integer} ttl : time to live of the cookie in millis. -1 to erase the cookie. - */ -function set_cookie(name, value, ttl) { - ttl = ttl || 24*60*60*365; - document.cookie = [ - name + '=' + value, - 'path=/', - 'max-age=' + ttl, - 'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString() - ].join(';'); -}; - -/** - * 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} number 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]; -} - -/** - * Naive and unlocalized human size parser. - * Returns the numeric amount specified in the given human formatted string. - * - * Eg: - * - * parse_human_size('4 Kb') === 4096 - * parse_human_size('10m') === 10485760 - * parse_human_size('340.99') === 340.99 - * - * @param {String} input human formatted string - */ -function parse_human_size(size) { - var units = "bkmgtpezy".split(''); - var parsed = size.toString().match(/^([0-9\.,]*)(\s*)?([a-z]{1,2})?$/i); - if (parsed === null) { - throw("Could not parse: " + size); - } - var amount = parseFloat(parsed[1].replace(',', '.')); - if (isNaN(amount) || !isFinite(amount)) { - throw("Invalid amount: " + size); - } - var unit = parsed[3] ? parsed[3][0].toLowerCase() : ''; - var index = units.indexOf(unit); - if (unit && index === -1) { - throw("Invalid unit: " + size); - } - if (index > 0) { - amount = amount * Math.pow(1024, index); - } - return amount; -} - -/** - * Returns a human readable number - * - * @param {Number} number - */ -function human_number (number, decimals) { - var decimals = decimals | 0; - var number = Math.round(number); - var d2 = Math.pow(10, decimals); - var val = _t("kMGTPE"); - var i = val.length-1, s; - while( i ) { - s = Math.pow(10,i--*3); - if( s <= number ) { - number = Math.round(number*d2/s)/d2 + val[i]; - } - } - return number; -} - -/** - * 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; - - /** - * Javascript performs strictly the round half up method, which is asymmetric. However, in - * Python, the method is symmetric. For example: - * - In JS, Math.round(-0.5) is equal to -0. - * - In Python, round(-0.5) is equal to -1. - * We want to keep the Python behavior for consistency. - */ - var sign = normalized_value < 0 ? -1.0 : 1.0; - var rounded_value = sign * Math.round(Math.abs(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; -}; - -/** - * Confines a value inside an interval - * @param {Number} [val] the value to confine - * @param {Number} [min] the minimum of the interval - * @param {Number} [max] the maximum of the interval - * @return val if val is in [min, max], min if val < min and max otherwise - */ -function confine (val, min, max) { - return Math.max(min, Math.min(max, val)); -} - -function assert (bool) { - if (!bool) { - throw new Error("AssertionError"); - } -} - -/* Logical XOR */ -function xor (a, b) { - return (a && !b) || (!a && b); -} - -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(); - }, -}); - -var DropPrevious = Class.extend({ - /** - * Registers a new deferred and rejects the previous one - * @param {$.Deferred} the new deferred - * @returns {$.Promise} - */ - add: function (deferred) { - if (this.current_def) { this.current_def.reject(); } - var res = $.Deferred(); - deferred.then(res.resolve, res.reject); - this.current_def = res; - return res.promise(); - } -}); - -/** - * Rejects a deferred as soon as a reference deferred is either resolved or rejected - * @param {$.Deferred} [target_def] the deferred to potentially reject - * @param {$.Deferred} [reference_def] the reference target - * @returns {$.Deferred} - */ -function reject_after(target_def, reference_def) { - var res = $.Deferred(); - target_def.then(res.resolve, res.reject); - reference_def.always(res.reject); - return res.promise(); -} - -/** - * Returns a deferred resolved after 'wait' milliseconds - * @param {int} [wait] the delay in ms - * @return {$.Deferred} - */ -function delay (wait) { - var def = $.Deferred(); - setTimeout(def.resolve, wait); - return def; -} - -function swap(array, elem1, elem2) { - var i1 = array.indexOf(elem1); - var i2 = array.indexOf(elem2); - array[i2] = elem1; - array[i1] = elem2; -} - -function toBoolElse (str, elseValues, trueValues, falseValues) { - var ret = _.str.toBool(str, trueValues, falseValues); - if (_.isUndefined(ret)) { - return elseValues; - } - return ret; -} - -function async_when() { - var async = false; - var def = $.Deferred(); - $.when.apply($, arguments).done(function() { - var args = arguments; - var action = function() { - def.resolve.apply(def, args); - }; - if (async) - action(); - else - setTimeout(action, 0); - }).fail(function() { - var args = arguments; - var action = function() { - def.reject.apply(def, args); - }; - if (async) - action(); - else - setTimeout(action, 0); - }); - async = true; - return def; -} - -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, - set_cookie: set_cookie, - insert_thousand_seps: insert_thousand_seps, - is_bin_size: is_bin_size, - binary_to_binsize: binary_to_binsize, - human_size: human_size, - parse_human_size: parse_human_size, - human_number: human_number, - round_precision: round_precision, - round_decimals: round_decimals, - float_is_zero: float_is_zero, - confine: confine, - assert: assert, - xor: xor, - DropMisordered: DropMisordered, - DropPrevious: DropPrevious, - reject_after: reject_after, - delay: delay, - swap: swap, - toBoolElse: toBoolElse, - async_when: async_when, -}; - -}); diff --git a/addons/web/static/src/js/framework/view.js b/addons/web/static/src/js/framework/view.js deleted file mode 100644 index c5a017710e9..00000000000 --- a/addons/web/static/src/js/framework/view.js +++ /dev/null @@ -1,211 +0,0 @@ -odoo.define('web.View', function (require) { -"use strict"; - -var core = require('web.core'); -var data = require('web.data'); -var data_manager = require('web.data_manager'); -var pyeval = require('web.pyeval'); -var Widget = require('web.Widget'); - -var View = Widget.extend({ - events: { - 'click a[type=action]': function (ev) { - ev.preventDefault(); - var action_data = $(ev.target).attr('name'); - this.do_action(action_data); - } - }, - defaults: { - action: {}, - }, - // name displayed in view switchers - display_name: '', - // used by views that need a searchview. - searchable: true, - // used by views that need a searchview but don't want it to be displayed. - searchview_hidden: false, - // multi_record is used to distinguish views displaying a single record - // (e.g. FormView) from those that display several records (e.g. ListView) - multi_record: true, - // indicates whether or not the view is mobile-friendly - mobile_friendly: false, - // icon is the font-awesome icon to display in the view switcher - icon: 'fa-question', - // whether or not the view requires the model's fields to render itself - require_fields: false, - init: function(parent, dataset, fields_view, options) { - this._super(parent); - this.ViewManager = parent; - this.dataset = dataset; - this.model = dataset.model; - this.fields_view = fields_view; - this.options = _.defaults({}, options, this.defaults); - }, - /** - * Triggers event 'view_loaded'. - * Views extending start() must call this.super() once they are ready. - * @return {Deferred} - */ - start: function() { - // add classname that reflect the (absence of) access rights - this.$el.toggleClass('o_cannot_create', !this.is_action_enabled('create')); - return this._super().then(this.trigger.bind(this, 'view_loaded')); - }, - /** - * Fetches and executes the action identified by ``action_data``. - * - * @param {Object} action_data the action descriptor data - * @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it - * @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``) - * @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'`` - * @param {Object} [action_data.context=null] additional action context, to add to the current context - * @param {DataSet} dataset a dataset object used to communicate with the server - * @param {Object} [record_id] the identifier of the object on which the action is to be applied - * @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action) - */ - do_execute_action: function (action_data, dataset, record_id, on_closed) { - var self = this; - var result_handler = function () { - if (on_closed) { on_closed.apply(null, arguments); } - if (self.getParent() && self.getParent().on_action_executed) { - return self.getParent().on_action_executed.apply(null, arguments); - } - }; - var context = new data.CompoundContext(dataset.get_context(), action_data.context || {}); - - // response handler - var handler = function (action) { - if (action && action.constructor === Object) { - // filter out context keys that are specific to the current action. - // Wrong default_* and search_default_* values will no give the expected result - // Wrong group_by values will simply fail and forbid rendering of the destination view - var ncontext = new data.CompoundContext( - _.object(_.reject(_.pairs(dataset.get_context().eval()), function(pair) { - return pair[0].match('^(?:(?:default_|search_default_|show_).+|.+_view_ref|group_by|group_by_no_leaf|active_id|active_ids)$') !== null; - })) - ); - ncontext.add(action_data.context || {}); - ncontext.add({active_model: dataset.model}); - if (record_id) { - ncontext.add({ - active_id: record_id, - active_ids: [record_id], - }); - } - ncontext.add(action.context || {}); - action.context = ncontext; - return self.do_action(action, { - on_close: result_handler, - }); - } else { - self.do_action({"type":"ir.actions.act_window_close"}); - return result_handler(); - } - }; - - if (action_data.special === 'cancel') { - return handler({"type":"ir.actions.act_window_close"}); - } else if (action_data.type === "object") { - var args = record_id ? [[record_id]] : [dataset.ids]; - if (action_data.args) { - try { - // Warning: quotes and double quotes problem due to json and xml clash - // Maybe we should force escaping in xml or do a better parse of the args array - var additional_args = JSON.parse(action_data.args.replace(/'/g, '"')); - args = args.concat(additional_args); - } catch(e) { - console.error("Could not JSON.parse arguments", action_data.args); - } - } - args.push(context); - return dataset.call_button(action_data.name, args).then(handler); - } else if (action_data.type === "action") { - return data_manager.load_action(action_data.name, _.extend(pyeval.eval('context', context), { - active_model: dataset.model, - active_ids: dataset.ids, - active_id: record_id - })).then(handler); - } - }, - do_show: function () { - this._super(); - core.bus.trigger('view_shown', this); - }, - do_push_state: function(state) { - if (this.getParent() && this.getParent().do_push_state) { - this.getParent().do_push_state(state); - } - }, - do_load_state: function (state, warm) { - }, - /** - * This function should render the action buttons of the view. - * It should be called after start(). - * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted - * $node may be undefined, in which case the View can insert the buttons somewhere else - */ - render_buttons: function($node) { - }, - /** - * This function should render the sidebar of the view. - * It should be called after start(). - * @param {jQuery} [$node] a jQuery node where the sidebar should be inserted - * $node may be undefined, in which case the View can insert the sidebar somewhere else - */ - render_sidebar: function($node) { - }, - /** - * This function should render the pager of the view. - * It should be called after start(). - * @param {jQuery} [$node] a jQuery node where the pager should be inserted - * $node may be undefined, in which case the View can insert the pager somewhere else - */ - render_pager: function($node) { - }, - /** - * Switches to a specific view type - */ - do_switch_view: function() { - this.trigger.apply(this, ['switch_mode'].concat(_.toArray(arguments))); - }, - do_search: function(domain, context, group_by) { - }, - sidebar_eval_context: function () { - return $.when({}); - }, - /** - * Asks the view to reload itself, if the reloading is asynchronous should - * return a {$.Deferred} indicating when the reloading is done. - */ - reload: function () { - return $.when(); - }, - /** - * Return whether the user can perform the action ('create', 'edit', 'delete') in this view. - * An action is disabled by setting the corresponding attribute in the view's main element, - * like: <form string="" create="false" edit="false" delete="false"> - */ - is_action_enabled: function(action) { - var attrs = this.fields_view.arch.attrs; - return (action in attrs) ? JSON.parse(attrs[action]) : true; - }, - get_context: function () { - return {}; - }, - destroy: function () { - if (this.$buttons) { - this.$buttons.off(); - } - return this._super.apply(this, arguments); - }, - set_scrollTop: function(scrollTop) { - this.scrollTop = scrollTop; - }, - get_scrollTop: function() { - return this.scrollTop; - } -}); - -return View; - -}); diff --git a/addons/web/static/src/js/framework/widget.js b/addons/web/static/src/js/framework/widget.js deleted file mode 100644 index 25a30ae6511..00000000000 --- a/addons/web/static/src/js/framework/widget.js +++ /dev/null @@ -1,426 +0,0 @@ -odoo.define('web.Widget', function (require) { -"use strict"; - -var core = require('web.core'); -var session = require('web.session'); - -var qweb = core.qweb; -var mixins = core.mixins; - -/** - * Base class for all visual components. Provides a lot of functionalities helpful - * for the management of a part of the DOM. - * - * Widget handles: - * - Rendering with QWeb. - * - Life-cycle management and parenting (when a parent is destroyed, all its children are - * destroyed too). - * - Insertion in DOM. - * - * Guide to create implementations of the Widget class: - * ============================================== - * - * Here is a sample child class: - * - * MyWidget = openerp.base.Widget.extend({ - * // the name of the QWeb template to use for rendering - * template: "MyQWebTemplate", - * - * init: function(parent) { - * this._super(parent); - * // stuff that you want to init before the rendering - * }, - * start: function() { - * // stuff you want to make after the rendering, `this.$el` holds a correct value - * this.$(".my_button").click(/* an example of event binding * /); - * - * // if you have some asynchronous operations, it's a good idea to return - * // a promise in start() - * var promise = this.rpc(...); - * return promise; - * } - * }); - * - * Now this class can simply be used with the following syntax: - * - * var my_widget = new MyWidget(this); - * my_widget.appendTo($(".some-div")); - * - * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the - * DOM inside the ".some-div" div and its events were binded. - * - * And of course, when you don't need that widget anymore, just do: - * - * my_widget.destroy(); - * - * That will kill the widget in a clean way and erase its content from the dom. - */ - - -var Widget = core.Class.extend(mixins.PropertiesMixin, { - // Backbone-ish API - tagName: 'div', - id: null, - className: null, - attributes: {}, - events: {}, - custom_events: {}, - /** - * The name of the QWeb template that will be used for rendering. Must be - * redefined in subclasses or the default render() method can not be used. - * - * @type string - */ - template: null, - /** - * Constructs the widget and sets its parent if a parent is given. - * - * @constructs openerp.Widget - * - * @param {openerp.Widget} parent Binds the current instance to the given Widget instance. - * When that widget is destroyed by calling destroy(), the current instance will be - * destroyed too. Can be null. - */ - init: function(parent) { - mixins.PropertiesMixin.init.call(this); - this.setParent(parent); - // Bind on_/do_* methods to this - // We might remove this automatic binding in the future - for (var name in this) { - if(typeof(this[name]) == "function") { - if((/^on_|^do_/).test(name)) { - this[name] = _.bind(this[name], this); - } - } - } - // FIXME: this should not be - this.setElement(this._make_descriptive()); - this.delegateCustomEvents(); - }, - /** - * Method called between init and start. Performs asynchronous calls required by start. - * - * This method should return a Deferred which is resolved when start can be executed. - * - * @return {jQuery.Deferred} - */ - willStart: function() { - return $.when(); - }, - /** - * Destroys the current widget, also destroys all its children before destroying itself. - */ - destroy: function() { - _.each(this.getChildren(), function(el) { - el.destroy(); - }); - if(this.$el) { - this.$el.remove(); - } - mixins.PropertiesMixin.destroy.call(this); - }, - /** - * Renders the current widget and appends it to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - appendTo: function(target) { - var self = this; - return this.__widgetRenderAndInsert(function(t) { - self.$el.appendTo(t); - }, target); - }, - /** - * Renders the current widget and prepends it to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - prependTo: function(target) { - var self = this; - return this.__widgetRenderAndInsert(function(t) { - self.$el.prependTo(t); - }, target); - }, - /** - * Renders the current widget and inserts it after to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - insertAfter: function(target) { - var self = this; - return this.__widgetRenderAndInsert(function(t) { - self.$el.insertAfter(t); - }, target); - }, - /** - * Renders the current widget and inserts it before to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - insertBefore: function(target) { - var self = this; - return this.__widgetRenderAndInsert(function(t) { - self.$el.insertBefore(t); - }, target); - }, - /** - * Attach the current widget to a dom element - * - * @param target A jQuery object or a Widget instance. - */ - attachTo: function(target) { - var self = this; - this.setElement(target.$el || target); - return this.willStart().then(function() { - return self.start(); - }); - }, - /** - * Renders the current widget and replaces the given jQuery object. - * - * @param target A jQuery object or a Widget instance. - */ - replace: function(target) { - return this.__widgetRenderAndInsert(_.bind(function(t) { - this.$el.replaceAll(t); - }, this), target); - }, - __widgetRenderAndInsert: function(insertion, target) { - var self = this; - return this.willStart().then(function() { - self.renderElement(); - insertion(target); - return self.start(); - }); - }, - /** - * Method called after rendering. Mostly used to bind actions, perform asynchronous - * calls, etc... - * - * By convention, this method should return an object that can be passed to $.when() - * to inform the caller when this widget has been initialized. - * - * @returns {jQuery.Deferred or any} - */ - start: function() { - return $.when(); - }, - /** - * Renders the element. The default implementation renders the widget using QWeb, - * `this.template` must be defined. The context given to QWeb contains the "widget" - * key that references `this`. - */ - renderElement: function() { - var $el; - if (this.template) { - $el = $(qweb.render(this.template, {widget: this}).trim()); - } else { - $el = this._make_descriptive(); - } - this.replaceElement($el); - }, - /** - * Re-sets the widget's root element and replaces the old root element - * (if any) by the new one in the DOM. - * - * @param {HTMLElement | jQuery} $el - * @returns {*} this - */ - replaceElement: function ($el) { - var $oldel = this.$el; - this.setElement($el); - if ($oldel && !$oldel.is(this.$el)) { - if ($oldel.length > 1) { - $oldel.wrapAll('<div/>'); - $oldel.parent().replaceWith(this.$el); - } else { - $oldel.replaceWith(this.$el); - } - } - return this; - }, - /** - * Re-sets the widget's root element (el/$el/$el). - * - * Includes: - * * re-delegating events - * * re-binding sub-elements - * * if the widget already had a root element, replacing the pre-existing - * element in the DOM - * - * @param {HTMLElement | jQuery} element new root element for the widget - * @return {*} this - */ - setElement: function (element) { - // NB: completely useless, as WidgetMixin#init creates a $el - // always - if (this.$el) { - this.undelegateEvents(); - } - - this.$el = (element instanceof $) ? element : $(element); - this.el = this.$el[0]; - - this.delegateEvents(); - - return this; - }, - /** - * Utility function to build small DOM elements. - * - * @param {String} tagName name of the DOM element to create - * @param {Object} [attributes] map of DOM attributes to set on the element - * @param {String} [content] HTML content to set on the element - * @return {Element} - */ - make: function (tagName, attributes, content) { - var el = document.createElement(tagName); - if (!_.isEmpty(attributes)) { - $(el).attr(attributes); - } - if (content) { - $(el).html(content); - } - return el; - }, - /** - * Makes a potential root element from the declarative builder of the - * widget - * - * @return {jQuery} - * @private - */ - _make_descriptive: function () { - var attrs = _.extend({}, this.attributes || {}); - if (this.id) { attrs.id = this.id; } - if (this.className) { attrs['class'] = this.className; } - return $(this.make(this.tagName, attrs)); - }, - delegateEvents: function () { - var events = this.events; - if (_.isEmpty(events)) { return; } - - for(var key in events) { - if (!events.hasOwnProperty(key)) { continue; } - - var method = this.proxy(events[key]); - - var match = /^(\S+)(\s+(.*))?$/.exec(key); - var event = match[1]; - var selector = match[3]; - - event += '.widget_events'; - if (!selector) { - this.$el.on(event, method); - } else { - this.$el.on(event, selector, method); - } - } - }, - delegateCustomEvents: function () { - if (_.isEmpty(this.custom_events)) { return; } - for (var key in this.custom_events) { - if (!this.custom_events.hasOwnProperty(key)) { continue; } - - var method = this.proxy(this.custom_events[key]); - this.on(key, this, wrap_handler(method)); - } - function wrap_handler(callback) { - return function (event) { - event.stop_propagation(); - callback(event); - }; - } - }, - undelegateEvents: function () { - this.$el.off('.widget_events'); - }, - /** - * Shortcut for ``this.$el.find(selector)`` - * - * @param {String} selector CSS selector, rooted in $el - * @returns {jQuery} selector match - */ - $: function(selector) { - if (selector === undefined) - return this.$el; - return this.$el.find(selector); - }, - /** - * Displays the widget - */ - do_show: function () { - this.$el.removeClass('o_hidden'); - }, - /** - * Hides the widget - */ - do_hide: function () { - this.$el.addClass('o_hidden'); - }, - /** - * Displays or hides the widget - * @param {Boolean} [display] use true to show the widget or false to hide it - */ - do_toggle: function (display) { - if (_.isBoolean(display)) { - display ? this.do_show() : this.do_hide(); - } else { - this.$el.hasClass('o_hidden') ? this.do_show() : this.do_hide(); - } - }, - /** - * Proxies a method of the object, in order to keep the right ``this`` on - * method invocations. - * - * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and - * even more so to ``jQuery.proxy`` with a fundamental difference: its - * resolution of the method being called is lazy, meaning it will use the - * method as it is when the proxy is called, not when the proxy is created. - * - * Other methods will fix the bound method to what it is when creating the - * binding/proxy, which is fine in most javascript code but problematic in - * OpenERP Web where developers may want to replace existing callbacks with - * theirs. - * - * The semantics of this precisely replace closing over the method call. - * - * @param {String|Function} method function or name of the method to invoke - * @returns {Function} proxied method - */ - proxy: function (method) { - var self = this; - return function () { - var fn = (typeof method === 'string') ? self[method] : method; - if (fn === void 0) { - throw new Error("Couldn't find method '" + method + "' in widget " + self); - } - return fn.apply(self, arguments); - }; - }, - /** - * Informs the action manager to do an action. This supposes that - * the action manager can be found amongst the ancestors of the current widget. - * If that's not the case this method will simply return `false`. - */ - do_action: function() { - var parent = this.getParent(); - if (parent) { - return parent.do_action.apply(parent, arguments); - } - return false; - }, - do_notify: function(title, message, sticky) { - this.trigger_up('notification', {title: title, message: message, sticky: sticky}); - }, - do_warn: function(title, message, sticky) { - this.trigger_up('warning', {title: title, message: message, sticky: sticky}); - }, - rpc: function(url, data, options) { - return this.alive(session.rpc(url, data, options)); - }, -}); - -return Widget; - -}); |
