diff options
| author | Xavier Morel <xmo@openerp.com> | 2011-08-16 16:25:15 +0200 |
|---|---|---|
| committer | Xavier Morel <xmo@openerp.com> | 2011-08-16 16:25:15 +0200 |
| commit | ffae7cb4b3e7350d19b273feb100ba73cd022214 (patch) | |
| tree | 15a06c6746e4d6f9d7042a269362ed72728eeceb /addons/base/static/src | |
| parent | d33c95fc4568516c22e12fdcce901d8250c9d218 (diff) | |
| parent | 36a1a0f1c3f6fe471453d52955f97110220599a3 (diff) | |
[MERGE] from trunk
bzr revid: xmo@openerp.com-20110816142515-ohq33zvua5uq8cd3
Diffstat (limited to 'addons/base/static/src')
| -rw-r--r-- | addons/base/static/src/js/boot.js | 24 | ||||
| -rw-r--r-- | addons/base/static/src/js/chrome.js | 343 | ||||
| -rw-r--r-- | addons/base/static/src/js/core.js | 432 | ||||
| -rw-r--r-- | addons/base/static/src/js/data.js | 245 | ||||
| -rw-r--r-- | addons/base/static/src/js/dates.js | 137 | ||||
| -rw-r--r-- | addons/base/static/src/js/form.js | 169 | ||||
| -rw-r--r-- | addons/base/static/src/js/formats.js | 208 | ||||
| -rw-r--r-- | addons/base/static/src/js/list.js | 130 | ||||
| -rw-r--r-- | addons/base/static/src/js/search.js | 168 | ||||
| -rw-r--r-- | addons/base/static/src/js/view_tree.js | 190 | ||||
| -rw-r--r-- | addons/base/static/src/js/views.js | 307 | ||||
| -rw-r--r-- | addons/base/static/src/xml/base.xml | 93 |
12 files changed, 1343 insertions, 1103 deletions
diff --git a/addons/base/static/src/js/boot.js b/addons/base/static/src/js/boot.js index 5754f5ccd14..3d1d1e75e7c 100644 --- a/addons/base/static/src/js/boot.js +++ b/addons/base/static/src/js/boot.js @@ -1,6 +1,6 @@ -//--------------------------------------------------------- -// OpenERP Web Boostrap -//--------------------------------------------------------- +/*--------------------------------------------------------- + * OpenERP Web Boostrap Code + *---------------------------------------------------------*/ /** * @name openerp @@ -14,13 +14,8 @@ /** @lends openerp */ var openerp = this.openerp = { - /** - * Debug flag turns on logging - */ + // debug flag debug: true, - // element_ids registry linked to all controllers on the page - // TODO rename to elements, or keep gtk naming? - screen: {}, // Per session namespace // openerp.<module> will map to // openerp.sessions.sessionname.<module> using a closure @@ -40,8 +35,6 @@ // this unique id will be replaced by hostname_databasename by // openerp.base.Connection on the first connection _session_id: "session" + session_counter++, - screen: openerp.screen, - sessions: openerp.sessions, base: {}, web_mobile: {} }; @@ -51,16 +44,17 @@ } return new_instance; } + // TODO add initrpc to init core only for RPC }; })(); -//--------------------------------------------------------- -// OpenERP base module split -//--------------------------------------------------------- +/*--------------------------------------------------------- + * OpenERP Web base module split + *---------------------------------------------------------*/ openerp.base = function(instance) { openerp.base.core(instance); - openerp.base.dates(instance); + openerp.base.formats(instance); openerp.base.chrome(instance); openerp.base.data(instance); if (openerp.base.views) { diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index ecce83342a2..927a000f002 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -1,287 +1,9 @@ /*--------------------------------------------------------- - * OpenERP base library + * OpenERP Web chrome *---------------------------------------------------------*/ openerp.base.chrome = function(openerp) { -openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{ - /** - * @constructs - * @param element_id to use for exception reporting - * @param server - * @param port - */ - init: function(parent, element_id, server, port) { - this._super(parent, element_id); - this.server = (server == undefined) ? location.hostname : server; - this.port = (port == undefined) ? location.port : port; - this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; - this.debug = true; - this.db = ""; - this.login = ""; - this.password = ""; - this.uid = false; - this.session_id = false; - this.module_list = []; - this.module_loaded = {"base": true}; - this.context = {}; - this.shortcuts = []; - this.active_id = null; - }, - start: function() { - this.session_restore(); - }, - /** - * 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 {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, success_callback, error_callback) { - var self = this; - // Construct a JSON-RPC2 request, method is currently unused - params.session_id = this.session_id; - - // Call using the rpc_mode - var deferred = $.Deferred(); - this.rpc_ajax(url, { - jsonrpc: "2.0", - method: "call", - params: params, - id:null - }).then(function () {deferred.resolve.apply(deferred, arguments);}, - function(error) {deferred.reject(error, $.Event());}); - return deferred.fail(function() { - deferred.fail(function(error, event) { - if (!event.isDefaultPrevented()) { - self.on_rpc_error(error, event); - } - }); - }).then(success_callback, error_callback).promise(); - }, - /** - * Raw JSON-RPC call - * - * @returns {jQuery.Deferred} ajax-based deferred object - */ - rpc_ajax: function(url, payload) { - var self = this; - this.on_rpc_request(); - // url can be an $.ajax option object - if (_.isString(url)) { - url = { - url: url - } - } - var ajax = _.extend({ - type: "POST", - url: url, - dataType: 'json', - contentType: 'application/json', - data: JSON.stringify(payload), - processData: false - }, url); - var deferred = $.Deferred(); - $.ajax(ajax).done(function(response, textStatus, jqXHR) { - self.on_rpc_response(); - if (!response.error) { - deferred.resolve(response["result"], textStatus, jqXHR); - return; - } - if (response.error.data.type !== "session_invalid") { - deferred.reject(response.error); - return; - } - self.uid = false; - self.on_session_invalid(function() { - self.rpc(url, payload.params, - function() { - deferred.resolve.apply(deferred, arguments); - }, - function(error, event) { - event.preventDefault(); - deferred.reject.apply(deferred, arguments); - }); - }); - }).fail(function(jqXHR, textStatus, errorThrown) { - self.on_rpc_response(); - var error = { - code: -32098, - message: "XmlHttpRequestError " + errorThrown, - data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } - }; - deferred.reject(error); - }); - return deferred.promise(); - }, - on_rpc_request: function() { - }, - on_rpc_response: function() { - }, - on_rpc_error: function(error) { - }, - /** - * The session is validated either by login or by restoration of a previous session - */ - on_session_valid: function() { - if(!openerp._modules_loaded) - this.load_modules(); - }, - on_session_invalid: function(contination) { - }, - session_is_valid: function() { - return this.uid; - }, - session_login: function(db, login, password, success_callback) { - var self = this; - this.db = db; - this.login = login; - this.password = password; - var params = { db: this.db, login: this.login, password: this.password }; - this.rpc("/base/session/login", params, function(result) { - self.session_id = result.session_id; - self.uid = result.uid; - self.session_save(); - self.on_session_valid(); - if (success_callback) - success_callback(); - }); - }, - session_logout: function() { - this.uid = false; - }, - /** - * Reloads uid and session_id from local storage, if they exist - */ - session_restore: function () { - this.uid = this.get_cookie('uid'); - this.session_id = this.get_cookie('session_id'); - this.db = this.get_cookie('db'); - this.login = this.get_cookie('login'); - // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login - // then call on_session_valid - this.on_session_valid(); - }, - /** - * Saves the session id and uid locally - */ - session_save: function () { - this.set_cookie('uid', this.uid); - this.set_cookie('session_id', this.session_id); - this.set_cookie('db', this.db); - this.set_cookie('login', this.login); - }, - logout: function() { - delete this.uid; - delete this.session_id; - delete this.db; - delete this.login; - this.set_cookie('uid', ''); - this.set_cookie('session_id', ''); - this.set_cookie('db', ''); - this.set_cookie('login', ''); - this.on_session_invalid(function() {}); - }, - /** - * Fetches a cookie stored by an openerp session - * - * @private - * @param name the cookie's name - */ - get_cookie: function (name) { - var nameEQ = this.element_id + '|' + 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) { - return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length))); - } - } - 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) { - ttl = ttl || 24*60*60*365; - document.cookie = [ - this.element_id + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)), - 'max-age=' + ttl, - 'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString() - ].join(';'); - }, - /** - * Load additional web addons of that instance and init them - */ - load_modules: function() { - var self = this; - this.rpc('/base/session/modules', {}, function(result) { - self.module_list = result; - var modules = self.module_list.join(','); - if(self.debug || true) { - self.rpc('/base/webclient/csslist', {"mods": modules}, self.do_load_css); - self.rpc('/base/webclient/jslist', {"mods": modules}, self.do_load_js); - } else { - self.do_load_css(["/base/webclient/css?mods="+modules]); - self.do_load_js(["/base/webclient/js?mods="+modules]); - } - openerp._modules_loaded = true; - }); - }, - do_load_css: function (files) { - _.each(files, function (file) { - $('head').append($('<link>', { - 'href': file, - 'rel': 'stylesheet', - 'type': 'text/css' - })); - }); - }, - do_load_js: function(files) { - var self = this; - if(files.length != 0) { - var file = files.shift(); - var tag = document.createElement('script'); - tag.type = 'text/javascript'; - tag.src = file; - tag.onload = tag.onreadystatechange = function() { - if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done ) - return; - tag.onload_done = true; - self.do_load_js(files); - }; - document.head.appendChild(tag); - } else { - this.on_modules_loaded(); - } - }, - on_modules_loaded: function() { - 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 - if(openerp._openerp[mod] != undefined) { - openerp._openerp[mod](openerp); - this.module_loaded[mod] = true; - } - } - } -}); - openerp.base.Notification = openerp.base.Widget.extend({ init: function(parent, element_id) { this._super(parent, element_id); @@ -307,10 +29,10 @@ openerp.base.Notification = openerp.base.Widget.extend({ openerp.base.Dialog = openerp.base.OldWidget.extend({ dialog_title: "", identifier_prefix: 'dialog', - init: function (parent, options) { + init: function (parent, dialog_options) { var self = this; this._super(parent); - this.options = { + this.dialog_options = { modal: true, width: 'auto', min_width: 0, @@ -320,27 +42,25 @@ openerp.base.Dialog = openerp.base.OldWidget.extend({ max_height: '100%', autoOpen: false, buttons: {}, - beforeClose: function () { - self.on_close(); - } + beforeClose: function () { self.on_close(); } }; for (var f in this) { if (f.substr(0, 10) == 'on_button_') { - this.options.buttons[f.substr(10)] = this[f]; + this.dialog_options.buttons[f.substr(10)] = this[f]; } } - if (options) { - this.set_options(options); + if (dialog_options) { + this.set_options(dialog_options); } }, set_options: function(options) { options = options || {}; - options.width = this.get_width(options.width || this.options.width); - options.min_width = this.get_width(options.min_width || this.options.min_width); - options.max_width = this.get_width(options.max_width || this.options.max_width); - options.height = this.get_height(options.height || this.options.height); - options.min_height = this.get_height(options.min_height || this.options.min_height); - options.max_height = this.get_height(options.max_height || this.options.max_width); + options.width = this.get_width(options.width || this.dialog_options.width); + options.min_width = this.get_width(options.min_width || this.dialog_options.min_width); + options.max_width = this.get_width(options.max_width || this.dialog_options.max_width); + options.height = this.get_height(options.height || this.dialog_options.height); + options.min_height = this.get_height(options.min_height || this.dialog_options.min_height); + options.max_height = this.get_height(options.max_height || this.dialog_options.max_width); if (options.width !== 'auto') { if (options.width > options.max_width) options.width = options.max_width; @@ -353,7 +73,7 @@ openerp.base.Dialog = openerp.base.OldWidget.extend({ if (!options.title && this.dialog_title) { options.title = this.dialog_title; } - _.extend(this.options, options); + _.extend(this.dialog_options, options); }, get_width: function(val) { return this.get_size(val.toString(), $(window.top).width()); @@ -370,20 +90,18 @@ openerp.base.Dialog = openerp.base.OldWidget.extend({ return parseInt(val, 10); } }, - start: function (auto_open) { - this.$dialog = $('<div id="' + this.element_id + '"></div>').dialog(this.options); - if (auto_open !== false) { - this.open(); - } + start: function () { + this.$dialog = $('<div id="' + this.element_id + '"></div>').dialog(this.dialog_options); this._super(); + return this; }, - open: function(options) { + open: function(dialog_options) { // TODO fme: bind window on resize if (this.template) { this.$element.html(this.render()); } - this.set_options(options); - this.$dialog.dialog(this.options).dialog('open'); + this.set_options(dialog_options); + this.$dialog.dialog(this.dialog_options).dialog('open'); }, close: function() { // Closes the dialog but leave it in a state where it could be opened again. @@ -913,7 +631,7 @@ openerp.base.Header = openerp.base.Widget.extend({ }, on_logout: function() { - this.$element.find('#shortcuts ul li').remove(); + this.$element.find('.oe-shortcuts ul').empty(); } }); @@ -1005,12 +723,10 @@ openerp.base.Homepage = openerp.base.Widget.extend({ openerp.base.Preferences = openerp.base.Widget.extend({ }); -openerp.base.ImportExport = openerp.base.Widget.extend({ -}); - openerp.base.WebClient = openerp.base.Widget.extend({ init: function(element_id) { this._super(null, element_id); + openerp.webclient = this; QWeb.add_template("/base/static/src/xml/base.xml"); var params = {}; @@ -1019,10 +735,10 @@ openerp.base.WebClient = openerp.base.Widget.extend({ } this.$element.html(QWeb.render("Interface", params)); - this.session = new openerp.base.Session(this,"oe_errors"); + this.session = new openerp.base.Session(); this.loading = new openerp.base.Loading(this,"oe_loading"); this.crashmanager = new openerp.base.CrashManager(this); - this.crashmanager.start(false); + this.crashmanager.start(); // Do you autorize this ? will be replaced by notify() in controller openerp.base.Widget.prototype.notification = new openerp.base.Notification(this, "oe_notification"); @@ -1038,8 +754,6 @@ openerp.base.WebClient = openerp.base.Widget.extend({ this.menu = new openerp.base.Menu(this, "oe_menu", "oe_secondary_menu"); this.menu.on_action.add(this.on_menu_action); this.header.on_action.add(this.on_menu_action); - - }, start: function() { this.session.start(); @@ -1049,8 +763,10 @@ openerp.base.WebClient = openerp.base.Widget.extend({ this.notification.notify("OpenERP Client", "The openerp client has been initialized."); }, on_logged: function() { - this.action_manager = new openerp.base.ActionManager(this, "oe_app"); - this.action_manager.start(); + if(this.action_manager) + this.action_manager.stop(); + this.action_manager = new openerp.base.ActionManager(this); + this.action_manager.appendTo($("#oe_app")); // if using saved actions, load the action and give it to action manager var parameters = jQuery.deparam(jQuery.param.querystring()); @@ -1090,7 +806,8 @@ openerp.base.WebClient = openerp.base.Widget.extend({ self.execute_home_action(home_action[0], ds); }) }, - default_home: function () { }, + default_home: function () { + }, /** * Bundles the execution of the home action * diff --git a/addons/base/static/src/js/core.js b/addons/base/static/src/js/core.js index 80dba3cd10c..96c6cb1372a 100644 --- a/addons/base/static/src/js/core.js +++ b/addons/base/static/src/js/core.js @@ -1,5 +1,5 @@ /*--------------------------------------------------------- - * OpenERP controller framework + * OpenERP Web core *--------------------------------------------------------*/ openerp.base.core = function(openerp) { @@ -305,16 +305,8 @@ openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Regis } }); -/** - * Utility class that any class is allowed to extend to easy common manipulations. - * - * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a - * logging facility. - */ -openerp.base.SessionAware = openerp.base.Class.extend({ - init: function(session) { - this.session = session; - +openerp.base.CallbackEnabled = openerp.base.Class.extend({ + init: function() { // Transform on_* method into openerp.base.callbacks for (var name in this) { if(typeof(this[name]) == "function") { @@ -325,6 +317,19 @@ openerp.base.SessionAware = openerp.base.Class.extend({ } } } + } +}); + +/** + * Utility class that any class is allowed to extend to easy common manipulations. + * + * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a + * logging facility. + */ +openerp.base.SessionAware = openerp.base.CallbackEnabled.extend({ + init: function(session) { + this._super(); + this.session = session; }, /** * Performs a JSON-RPC call @@ -373,59 +378,6 @@ openerp.base.SessionAware = openerp.base.Class.extend({ } }); -/** - * 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. - * - * Widget also extends SessionAware for ease of use. - * - * 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", - * // identifier prefix, it is useful to put an obvious one for debugging - * identifier_prefix: 'my-id-prefix-', - * - * init: function(parent) { - * this._super(parent); - * // stuff that you want to init before the rendering - * }, - * start: function() { - * this._super(); - * // stuff you want to make after the rendering, `this.$element` holds a correct value - * this.$element.find(".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.stop(); - * - * That will kill the widget in a clean way and erase its content from the dom. - */ openerp.base.Widget = openerp.base.SessionAware.extend({ /** * The name of the QWeb template that will be used for rendering. Must be @@ -574,6 +526,18 @@ openerp.base.Widget = openerp.base.SessionAware.extend({ } return false; }, + do_notify: function() { + if (this.widget_parent) { + return this.widget_parent.do_notify.apply(this,arguments); + } + return false; + }, + do_warn: function() { + if (this.widget_parent) { + return this.widget_parent.do_warn.apply(this,arguments); + } + return false; + }, rpc: function(url, data, success, error) { var def = $.Deferred().then(success, error); var self = this; @@ -599,5 +563,345 @@ openerp.base.OldWidget = openerp.base.Widget.extend({ } }); +openerp.base.TranslationDataBase = openerp.base.Class.extend({ + init: function() { + this.db = {}; + 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.db = {}; + var modules = _.keys(translation_bundle.modules).sort(); + if (_.include(modules, "base")) { + modules = ["base"].concat(_.without(modules, "base")); + } + _.each(modules, function(name) { + self.add_module_translation(translation_bundle.modules[name]); + }); + if (translation_bundle.lang_parameters) { + this.parameters = translation_bundle.lang_parameters; + } + }, + add_module_translation: function(mod) { + var self = this; + _.each(mod.messages, function(message) { + if (self.db[message.id] === undefined) { + 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) { + if (this.db[key]) + return this.db[key]; + return undefined; + } +}); + +openerp.base._t = new openerp.base.TranslationDataBase().build_translation_function(); + +openerp.base.Session = openerp.base.CallbackEnabled.extend( /** @lends openerp.base.Session# */{ + /** + * @constructs + * @param element_id to use for exception reporting + * @param server + * @param port + */ + init: function(server, port) { + this._super(); + this.server = (server == undefined) ? location.hostname : server; + this.port = (port == undefined) ? location.port : port; + this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; + this.debug = true; + this.db = ""; + this.login = ""; + this.password = ""; + this.user_context= {}; + this.uid = false; + this.session_id = false; + this.module_list = []; + this.module_loaded = {"base": true}; + this.context = {}; + this.shortcuts = []; + this.active_id = null; + }, + start: function() { + this.session_restore(); + }, + /** + * 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 {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, success_callback, error_callback) { + var self = this; + // Construct a JSON-RPC2 request, method is currently unused + params.session_id = this.session_id; + + // Call using the rpc_mode + var deferred = $.Deferred(); + this.rpc_ajax(url, { + jsonrpc: "2.0", + method: "call", + params: params, + id:null + }).then(function () {deferred.resolve.apply(deferred, arguments);}, + function(error) {deferred.reject(error, $.Event());}); + return deferred.fail(function() { + deferred.fail(function(error, event) { + if (!event.isDefaultPrevented()) { + self.on_rpc_error(error, event); + } + }); + }).then(success_callback, error_callback).promise(); + }, + /** + * Raw JSON-RPC call + * + * @returns {jQuery.Deferred} ajax-based deferred object + */ + rpc_ajax: function(url, payload) { + var self = this; + this.on_rpc_request(); + // url can be an $.ajax option object + if (_.isString(url)) { + url = { + url: url + } + } + var ajax = _.extend({ + type: "POST", + url: url, + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify(payload), + processData: false + }, url); + var deferred = $.Deferred(); + $.ajax(ajax).done(function(response, textStatus, jqXHR) { + self.on_rpc_response(); + if (!response.error) { + deferred.resolve(response["result"], textStatus, jqXHR); + return; + } + if (response.error.data.type !== "session_invalid") { + deferred.reject(response.error); + return; + } + self.uid = false; + self.on_session_invalid(function() { + self.rpc(url, payload.params, + function() { + deferred.resolve.apply(deferred, arguments); + }, + function(error, event) { + event.preventDefault(); + deferred.reject.apply(deferred, arguments); + }); + }); + }).fail(function(jqXHR, textStatus, errorThrown) { + self.on_rpc_response(); + var error = { + code: -32098, + message: "XmlHttpRequestError " + errorThrown, + data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } + }; + deferred.reject(error); + }); + return deferred.promise(); + }, + on_rpc_request: function() { + }, + on_rpc_response: function() { + }, + on_rpc_error: function(error) { + }, + /** + * The session is validated either by login or by restoration of a previous session + */ + on_session_valid: function() { + if(!openerp._modules_loaded) + this.load_modules(); + }, + on_session_invalid: function(contination) { + }, + session_is_valid: function() { + return this.uid; + }, + session_login: function(db, login, password, success_callback) { + var self = this; + this.db = db; + this.login = login; + this.password = password; + var params = { db: this.db, login: this.login, password: this.password }; + this.rpc("/base/session/login", params, function(result) { + self.session_id = result.session_id; + self.uid = result.uid; + self.user_context = result.context; + self.session_save(); + self.on_session_valid(); + if (success_callback) + success_callback(); + }); + }, + session_logout: function() { + this.uid = false; + }, + /** + * Reloads uid and session_id from local storage, if they exist + */ + session_restore: function () { + this.uid = this.get_cookie('uid'); + this.session_id = this.get_cookie('session_id'); + this.db = this.get_cookie('db'); + this.login = this.get_cookie('login'); + this.user_context = this.get_cookie("user_context"); + // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login + // then call on_session_valid + this.on_session_valid(); + }, + /** + * Saves the session id and uid locally + */ + session_save: function () { + this.set_cookie('uid', this.uid); + this.set_cookie('session_id', this.session_id); + this.set_cookie('db', this.db); + this.set_cookie('login', this.login); + this.set_cookie('user_context', this.user_context); + }, + logout: function() { + delete this.uid; + delete this.session_id; + delete this.db; + delete this.login; + this.set_cookie('uid', ''); + this.set_cookie('session_id', ''); + this.set_cookie('db', ''); + this.set_cookie('login', ''); + this.on_session_invalid(function() {}); + }, + /** + * Fetches a cookie stored by an openerp session + * + * @private + * @param name the cookie's name + */ + get_cookie: function (name) { + var nameEQ = this.element_id + '|' + 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) { + return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length))); + } + } + 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) { + ttl = ttl || 24*60*60*365; + document.cookie = [ + this.element_id + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)), + 'max-age=' + ttl, + 'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString() + ].join(';'); + }, + /** + * Load additional web addons of that instance and init them + */ + load_modules: function() { + var self = this; + this.rpc('/base/session/modules', {}, function(result) { + self.module_list = result; + var lang = self.user_context.lang; + self.rpc('/base/webclient/translations',{ + mods: ["base"].concat(result), + lang: lang}) + .then(function(transs) { + openerp.base._t.database.set_bundle(transs); + var modules = self.module_list.join(','); + if(self.debug || true) { + self.rpc('/base/webclient/csslist', {"mods": modules}, self.do_load_css); + self.rpc('/base/webclient/jslist', {"mods": modules}, self.do_load_js); + } else { + self.do_load_css(["/base/webclient/css?mods="+modules]); + self.do_load_js(["/base/webclient/js?mods="+modules]); + } + openerp._modules_loaded = true; + }); + }); + }, + do_load_css: function (files) { + _.each(files, function (file) { + $('head').append($('<link>', { + 'href': file, + 'rel': 'stylesheet', + 'type': 'text/css' + })); + }); + }, + do_load_js: function(files) { + var self = this; + if(files.length != 0) { + var file = files.shift(); + var tag = document.createElement('script'); + tag.type = 'text/javascript'; + tag.src = file; + tag.onload = tag.onreadystatechange = function() { + if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done ) + return; + tag.onload_done = true; + self.do_load_js(files); + }; + document.head.appendChild(tag); + } else { + this.on_modules_loaded(); + } + }, + on_modules_loaded: function() { + 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 + if(openerp._openerp[mod] != undefined) { + openerp._openerp[mod](openerp); + this.module_loaded[mod] = true; + } + } + } +}); + }; + // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index fb926a2a592..244c057d544 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -268,9 +268,13 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data }, /** * 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 {Function} callback function called with read result + * @returns {$.Deferred} */ read_ids: function (ids, fields, callback) { - var self = this; return this.rpc('/base/dataset/get', { model: this.model, ids: ids, @@ -282,13 +286,20 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data * Read a slice of the records represented by this DataSet, based on its * domain and context. * - * @param {Number} [offset=0] The index from which selected records should be returned - * @param {Number} [limit=null] The maximum number of records to return + * @params {Object} options + * @param {Array} [options.fields] fields to read and return, by default all fields are returned + * @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 + * @param {Function} callback function called with read_slice result + * @returns {$.Deferred} */ - read_slice: function (fields, offset, limit, callback) { - }, + read_slice: function (options, callback) { return null; }, /** - * Read the indexed record. + * Reads the current dataset record (from its index) + * + * @params {Array} [fields] fields to read and return, by default all fields are returned + * @params {Function} callback function called with read_index result + * @returns {$.Deferred} */ read_index: function (fields, callback) { var def = $.Deferred().then(callback); @@ -304,6 +315,13 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data } return def.promise(); }, + /** + * Reads default values for the current model + * + * @param {Array} [fields] fields to get default values for, by default all defaults are read + * @param {Function} callback function called with default_get result + * @returns {$.Deferred} + */ default_get: function(fields, callback) { return this.rpc('/base/dataset/default_get', { model: this.model, @@ -311,6 +329,14 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data context: this.get_context() }, callback); }, + /** + * Creates a new record in db + * + * @param {Object} data field values to set on the new record + * @param {Function} callback function called with operation result + * @param {Function} error_callback function called in case of creation error + * @returns {$.Deferred} + */ create: function(data, callback, error_callback) { return this.rpc('/base/dataset/create', { model: this.model, @@ -318,6 +344,15 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data context: this.get_context() }, callback, error_callback); }, + /** + * 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 {Function} callback function called with operation result + * @param {Function} error_callback function called in case of write error + * @returns {$.Deferred} + */ write: function (id, data, callback, error_callback) { return this.rpc('/base/dataset/save', { model: this.model, @@ -326,11 +361,27 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data context: this.get_context() }, callback, error_callback); }, + /** + * Deletes an existing record from the database + * + * @param {Number|String} ids identifier of the record to delete + * @param {Function} callback function called with operation result + * @param {Function} error_callback function called in case of deletion error + */ unlink: function(ids, callback, error_callback) { var self = this; return this.call_and_eval("unlink", [ids, this.get_context()], null, 1, callback, error_callback); }, + /** + * 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, callback, error_callback) { return this.rpc('/base/dataset/call', { model: this.model, @@ -338,6 +389,17 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data args: args || [] }, callback, error_callback); }, + /** + * Calls an arbitrary method, with more crazy + * + * @param {String} method + * @param {Array} [args] + * @param {Number} [domain_id] index of a domain to evaluate in the args array + * @param {Number} [context_id] index of a context to evaluate in the args array + * @param {Function} callback + * @param {Function }error_callback + * @returns {$.Deferred} + */ call_and_eval: function (method, args, domain_id, context_id, callback, error_callback) { return this.rpc('/base/dataset/call', { model: this.model, @@ -347,6 +409,15 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data args: args || [] }, callback, error_callback); }, + /** + * Calls a button method, usually returning some sort of action + * + * @param {String} method + * @param {Array} [args] + * @param {Function} callback + * @param {Function} error_callback + * @returns {$.Deferred} + */ call_button: function (method, args, callback, error_callback) { return this.rpc('/base/dataset/call_button', { model: this.model, @@ -356,17 +427,34 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data args: args || [] }, callback, error_callback); }, + /** + * Fetches the "readable name" for records, based on intrinsic rules + * + * @param {Array} ids + * @param {Function} callback + * @returns {$.Deferred} + */ name_get: function(ids, callback) { return this.call_and_eval('name_get', [ids, this.get_context()], null, 1, callback); }, - /* - * args = domain + /** + * + * @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=100] maximum number of matches to return + * @param {Function} callback function to call with name_search result + * @returns {$.Deferred} */ - name_search: function (name, args, operator, limit, callback) { + name_search: function (name, domain, operator, limit, callback) { return this.call_and_eval('name_search', - [name || '', args || false, operator || 'ilike', this.get_context(), limit || 100], + [name || '', domain || false, operator || 'ilike', this.get_context(), limit || 100], 1, 3, callback); }, + /** + * @param name + * @param callback + */ name_create: function(name, callback) { return this.call_and_eval('name_create', [name, this.get_context()], null, 1, callback); }, @@ -377,7 +465,10 @@ openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.Data signal: signal }, callback); }, - get_context: function() { + get_context: function(request_context) { + if (request_context) { + return new openerp.base.CompoundContext(this.context, request_context); + } return this.context; } }); @@ -387,9 +478,11 @@ openerp.base.DataSetStatic = openerp.base.DataSet.extend({ // all local records this.ids = ids || []; }, - read_slice: function (fields, offset, limit, callback) { - var self = this; - offset = offset || 0; + read_slice: function (options, callback) { + var self = this, + offset = options.offset || 0, + limit = options.limit || false, + fields = options.fields || false; var end_pos = limit && limit !== -1 ? offset + limit : undefined; return this.read_ids(this.ids.slice(offset, end_pos), fields, callback); }, @@ -426,25 +519,30 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({ // is it necessary ? this.ids = []; }, - read_slice: function (fields, offset, limit, callback) { + /** + * 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 + * @param {Function} callback function called with read_slice result + * @returns {$.Deferred} + */ + read_slice: function (options, callback) { var self = this; - offset = offset || 0; - // cached search, not sure it's a good idea - if(this.offset <= offset) { - var start = offset - this.offset; - if(this.ids.length - start >= limit) { - // TODO: check if this could work do only read if possible - // return read_ids(ids.slice(start,start+limit),fields,callback) - } - } + var offset = options.offset || 0; return this.rpc('/base/dataset/search_read', { model: this.model, - fields: fields, - domain: this.domain, - context: this.get_context(), + fields: options.fields || false, + domain: this.get_domain(options.domain), + context: this.get_context(options.context), sort: this.sort(), offset: offset, - limit: limit + limit: options.limit || false }, function (result) { self.ids = result.ids; self.offset = offset; @@ -453,6 +551,12 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({ } }); }, + get_domain: function (other_domain) { + if (other_domain) { + return new openerp.base.CompoundDomain(this.domain, other_domain); + } + return this.domain; + }, /** * Reads or changes sort criteria on the dataset. * @@ -628,48 +732,51 @@ openerp.base.ReadOnlyDataSetSearch = openerp.base.DataSetSearch.extend({ on_unlink: function(ids) {} }); -openerp.base.CompoundContext = function() { - this.__ref = "compound_context"; - this.__contexts = []; - this.__eval_context = null; - var self = this; - _.each(arguments, function(x) { - self.add(x); - }); -}; -openerp.base.CompoundContext.prototype.add = function(context) { - this.__contexts.push(context); - return this; -}; -openerp.base.CompoundContext.prototype.set_eval_context = function(eval_context) { - this.__eval_context = eval_context; - return this; -}; -openerp.base.CompoundContext.prototype.get_eval_context = function() { - return this.__eval_context; -}; - -openerp.base.CompoundDomain = function() { - this.__ref = "compound_domain"; - this.__domains = []; - this.__eval_context = null; - var self = this; - _.each(arguments, function(x) { - self.add(x); - }); -}; -openerp.base.CompoundDomain.prototype.add = function(domain) { - this.__domains.push(domain); - return this; -}; -openerp.base.CompoundDomain.prototype.set_eval_context = function(eval_context) { - this.__eval_context = eval_context; - return this; -}; -openerp.base.CompoundDomain.prototype.get_eval_context = function() { - return this.__eval_context; -}; +openerp.base.CompoundContext = openerp.base.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; + } +}); +openerp.base.CompoundDomain = openerp.base.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; + } +}); }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/dates.js b/addons/base/static/src/js/dates.js deleted file mode 100644 index 8daea39773f..00000000000 --- a/addons/base/static/src/js/dates.js +++ /dev/null @@ -1,137 +0,0 @@ - -openerp.base.dates = function(openerp) { - -/** - * Converts a string to a Date javascript object using OpenERP's - * datetime string format (exemple: '2011-12-01 15:12:35'). - * - * The timezone is assumed to be UTC (standard for OpenERP 6.1) - * and will be converted to the browser's timezone. - * - * @param {String} str A string representing a datetime. - * @returns {Date} - */ -openerp.base.parse_datetime = function(str) { - if(!str) { - return str; - } - var regex = /\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/; - var res = regex.exec(str); - if ( res[0] != str ) { - throw "'" + str + "' is not a valid datetime"; - } - var obj = Date.parse(str + " GMT"); - if (! obj) { - throw "'" + str + "' is not a valid datetime"; - } - return obj; -}; - -/** - * Converts a string to a Date javascript object using OpenERP's - * date string format (exemple: '2011-12-01'). - * - * @param {String} str A string representing a date. - * @returns {Date} - */ -openerp.base.parse_date = function(str) { - if(!str) { - return str; - } - var regex = /\d\d\d\d-\d\d-\d\d/; - var res = regex.exec(str); - if ( res[0] != str ) { - throw "'" + str + "' is not a valid date"; - } - var obj = Date.parse(str); - if (! obj) { - throw "'" + str + "' is not a valid date"; - } - return obj; -}; - -/** - * Converts a string to a Date javascript object using OpenERP's - * time string format (exemple: '15:12:35'). - * - * @param {String} str A string representing a time. - * @returns {Date} - */ -openerp.base.parse_time = function(str) { - if(!str) { - return str; - } - var regex = /\d\d:\d\d:\d\d/; - var res = regex.exec(str); - if ( res[0] != str ) { - throw "'" + str + "' is not a valid time"; - } - var obj = Date.parse(str); - if (! obj) { - throw "'" + str + "' is not a valid time"; - } - return obj; -}; - -/* - * Just a simple function to add some '0' if an integer it too small. - */ -var fts = function(str, size) { - str = "" + str; - var to_add = ""; - _.each(_.range(size - str.length), function() { - to_add = to_add + "0"; - }); - return to_add + str; -}; - -/** - * Converts a Date javascript object to a string using OpenERP's - * datetime string format (exemple: '2011-12-01 15:12:35'). - * - * The timezone 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. - */ -openerp.base.format_datetime = function(obj) { - if (!obj) { - return false; - } - return fts(obj.getUTCFullYear(),4) + "-" + fts(obj.getUTCMonth() + 1,2) + "-" - + fts(obj.getUTCDate(),2) + " " + fts(obj.getUTCHours(),2) + ":" - + fts(obj.getUTCMinutes(),2) + ":" + fts(obj.getUTCSeconds(),2); -}; - -/** - * Converts a Date javascript object to a string using OpenERP's - * date string format (exemple: '2011-12-01'). - * - * @param {Date} obj - * @returns {String} A string representing a date. - */ -openerp.base.format_date = function(obj) { - if (!obj) { - return false; - } - return fts(obj.getFullYear(),4) + "-" + fts(obj.getMonth() + 1,2) + "-" - + fts(obj.getDate(),2); -}; - -/** - * Converts a Date javascript object to a string using OpenERP's - * time string format (exemple: '15:12:35'). - * - * @param {Date} obj - * @returns {String} A string representing a time. - */ -openerp.base.format_time = function(obj) { - if (!obj) { - return false; - } - return fts(obj.getHours(),2) + ":" + fts(obj.getMinutes(),2) + ":" - + fts(obj.getSeconds(),2); -}; - -}; diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 628567b42ef..6eeff025e1f 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -1,4 +1,6 @@ openerp.base.form = function (openerp) { + +var _t = openerp.base._t; openerp.base.views.add('form', 'openerp.base.FormView'); openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{ @@ -19,7 +21,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi */ init: function(parent, element_id, dataset, view_id, options) { this._super(parent, element_id); - this.set_default_options(); + this.set_default_options(options); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -36,7 +38,6 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi this.registry = openerp.base.form.widgets; this.has_been_loaded = $.Deferred(); this.$form_header = null; - this.options = options || {}; _.defaults(this.options, {"always_show_new_button": true}); }, start: function() { @@ -156,7 +157,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi if (this.sidebar) { this.sidebar.attachments.do_update(); } - if (this.default_focus_field) { + if (this.default_focus_field && !this.embedded_view) { this.default_focus_field.focus(); } }, @@ -452,12 +453,14 @@ openerp.base.form.SidebarAttachments = openerp.base.Widget.extend({ this.on_attachments_loaded([]); } else { (new openerp.base.DataSetSearch( - this, 'ir.attachment', this.view.dataset.get_context(), - [['res_model', '=', this.view.dataset.model], - ['res_id', '=', this.view.datarecord.id], - ['type', 'in', ['binary', 'url']]])).read_slice( - ['name', 'url', 'type'], false, false, - this.on_attachments_loaded); + this, 'ir.attachment', this.view.dataset.get_context(), + [ + ['res_model', '=', this.view.dataset.model], + ['res_id', '=', this.view.datarecord.id], + ['type', 'in', ['binary', 'url']] + ])).read_slice( + {fields: ['name', 'url', 'type']}, + this.on_attachments_loaded); } }, on_attachments_loaded: function(attachments) { @@ -511,36 +514,40 @@ openerp.base.form.compute_domain = function(expr, fields) { } } - var field = fields[ex[0]].get_value ? fields[ex[0]].get_value() : fields[ex[0]].value; + var field = fields[ex[0]]; + if (!field) { + throw new Error("Domain references unknown field : " + ex[0]); + } + var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value; var op = ex[1]; var val = ex[2]; switch (op.toLowerCase()) { case '=': case '==': - stack.push(field == val); + stack.push(field_value == val); break; case '!=': case '<>': - stack.push(field != val); + stack.push(field_value != val); break; case '<': - stack.push(field < val); + stack.push(field_value < val); break; case '>': - stack.push(field > val); + stack.push(field_value > val); break; case '<=': - stack.push(field <= val); + stack.push(field_value <= val); break; case '>=': - stack.push(field >= val); + stack.push(field_value >= val); break; case 'in': - stack.push(_(val).contains(field)); + stack.push(_(val).contains(field_value)); break; case 'not in': - stack.push(!_(val).contains(field)); + stack.push(!_(val).contains(field_value)); break; default: this.log("Unsupported operator in modifiers :", op); @@ -679,6 +686,16 @@ openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({ start: function() { this._super.apply(this, arguments); this.$element.tabs(); + this.view.on_button_new.add_last(this.do_select_first_visible_tab); + }, + do_select_first_visible_tab: function() { + for (var i = 0; i < this.pages.length; i++) { + var page = this.pages[i]; + if (page.invisible === false) { + this.$element.tabs('select', page.index); + break; + } + } } }); @@ -696,8 +713,8 @@ openerp.base.form.WidgetNotebookPage = openerp.base.form.WidgetFrame.extend({ this.$element_tab = $('#' + this.element_tab_id); }, update_dom: function() { - if (this.invisible) { - this.notebook.$element.tabs('select', 0); + if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) { + this.notebook.do_select_first_visible_tab(); } this.$element_tab.toggle(!this.invisible); this.$element.toggle(!this.invisible); @@ -758,8 +775,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ var self = this; this.view.execute_action( - this.node.attrs, this.view.dataset, this.session.action_manager, - this.view.datarecord.id, function () { + this.node.attrs, this.view.dataset, this.view.datarecord.id, function () { self.view.reload(); }); } @@ -794,7 +810,7 @@ openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({ var self = this; this.$element.find("label").dblclick(function() { var widget = self['for'] || self; - console.log(widget.element_id , widget); + self.log(widget.element_id , widget); }); } }); @@ -1306,7 +1322,7 @@ openerp.base.form.dialog = function(content, options) { }, options || {}); options.autoOpen = true; var dialog = new openerp.base.Dialog(null, options); - dialog.$dialog = $(content).dialog(dialog.options); + dialog.$dialog = $(content).dialog(dialog.dialog_options); return dialog.$dialog; } @@ -1440,7 +1456,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ // search more... if more results that max if (values.length > self.limit) { values = values.slice(0, self.limit); - values.push({label: "<em>Â Â Â Search More...</em>", action: function() { + values.push({label: _t("<em>Â Â Â Search More...</em>"), action: function() { dataset.name_search(search_val, self.build_domain(), 'ilike' , false, function(data) { self._change_int_value(null); @@ -1453,13 +1469,13 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ if (search_val.length > 0 && !_.include(raw_result, search_val) && (!self.value || search_val !== self.value[1])) { - values.push({label: '<em>Â Â Â Create "<strong>' + - $('<span />').text(search_val).html() + '</strong>"</em>', action: function() { + values.push({label: _.sprintf(_t('<em>Â Â Â Create "<strong>%s</strong>"</em>'), + $('<span />').text(search_val).html()), action: function() { self._quick_create(search_val); }}); } // create... - values.push({label: "<em>Â Â Â Create and Edit...</em>", action: function() { + values.push({label: _t("<em>Â Â Â Create and Edit...</em>"), action: function() { self._change_int_value(null); self._search_create_popup("form", undefined, {"default_name": search_val}); }}); @@ -1492,7 +1508,6 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ var dataset = new openerp.base.DataSetStatic(self, self.field.relation, self.build_context()); dataset.name_get([element_ids[0]], function(data) { self._change_int_ext_value(data[0]); - pop.stop(); }); }); }, @@ -1624,20 +1639,22 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ modes = !!modes ? modes.split(",") : ["tree", "form"]; var views = []; _.each(modes, function(mode) { - var view = {view_id: false, view_type: mode == "tree" ? "list" : mode}; + var view = { + view_id: false, + view_type: mode == "tree" ? "list" : mode, + options: { sidebar : false } + }; if (self.field.views && self.field.views[mode]) { view.embedded_view = self.field.views[mode]; } if(view.view_type === "list") { - view.options = { - 'selectable': self.multi_selection - }; + view.options.selectable = self.multi_selection; } views.push(view); }); this.views = views; - this.viewmanager = new openerp.base.ViewManager(this, this.element_id, this.dataset, views); + this.viewmanager = new openerp.base.ViewManager(this, this.dataset, views); this.viewmanager.registry = openerp.base.views.clone({ list: 'openerp.base.form.One2ManyListView', form: 'openerp.base.form.One2ManyFormView' @@ -1663,7 +1680,7 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ self.save_form_view(); }); setTimeout(function () { - self.viewmanager.start(); + self.viewmanager.appendTo(self.$element); }, 0); }, reload_current_view: function() { @@ -1826,16 +1843,16 @@ openerp.base.form.One2ManyListView = openerp.base.ListView.extend({ pop.select_element(self.o2m.field.relation,{ initial_view: "form", alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, - auto_create: false, + create_function: function(data) { + return self.o2m.dataset.create(data, function(r) { + self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); + self.o2m.dataset.on_change(); + }); + }, parent_view: self.o2m.view }, self.o2m.build_domain(), self.o2m.build_context()); - pop.on_create.add(function(data) { - self.o2m.dataset.create(data, function(r) { - self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); - self.o2m.dataset.on_change(); - pop.stop(); - self.o2m.reload_current_view(); - }); + pop.on_select_elements.add_last(function() { + self.o2m.reload_current_view(); }); } }, @@ -1874,7 +1891,7 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ self.on_ui_change(); }); - this.list_view = new openerp.base.form.Many2ManyListView(new openerp.base.NullViewManager(this), this.list_id, this.dataset, false, { + this.list_view = new openerp.base.form.Many2ManyListView(this, this.list_id, this.dataset, false, { 'addable': 'Add', 'selectable': self.multi_selection }); @@ -1930,7 +1947,6 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ self.reload_content(); } }); - pop.stop(); }); }, do_activate_record: function(index, id) { @@ -1952,16 +1968,22 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ * - initial_view: form or search (default search) * - disable_multiple_selection * - alternative_form_view - * - auto_create (default true) + * - create_function (defaults to a naive saving behavior) * - parent_view */ select_element: function(model, options, domain, context) { + var self = this; this.model = model; this.domain = domain || []; this.context = context || {}; - this.options = _.defaults(options || {}, {"initial_view": "search", "auto_create": true}); + this.options = _.defaults(options || {}, {"initial_view": "search", "create_function": function() { + return self.create_row.apply(self, arguments); + }}); this.initial_ids = this.options.initial_ids; - openerp.base.form.dialog(this.render()); + this.created_elements = []; + openerp.base.form.dialog(this.render(), {close:function() { + self.check_exit(); + }}); this.start(); }, start: function() { @@ -2007,6 +2029,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ } $sbutton.click(function() { self.on_select_elements(self.selected_ids); + self.stop(); }); self.view_list = new openerp.base.form.SelectCreateListView(self, self.element_id + "_view_list", self.dataset, false, @@ -2019,15 +2042,11 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ }); this.searchview.start(); }, - on_create: function(data) { - if (!this.options.auto_create) - return; + create_row: function(data) { var self = this; var wdataset = new openerp.base.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; - wdataset.create(data, function(r) { - self.on_select_elements([r.result]); - }); + return wdataset.create(data); }, on_select_elements: function(element_ids) { }, @@ -2055,18 +2074,43 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ this.view_form.start(); this.view_form.on_loaded.add_last(function() { var $buttons = self.view_form.$element.find(".oe_form_buttons"); - $buttons.html(QWeb.render("SelectCreatePopup.form.buttons")); + $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self})); + var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save-new"); + $nbutton.click(function() { + self._created = $.Deferred().then(function() { + self._created = undefined; + self.view_form.on_button_new(); + }); + self.view_form.do_save(); + }); var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save"); $nbutton.click(function() { + self._created = $.Deferred().then(function() { + self._created = undefined; + self.check_exit(); + }); self.view_form.do_save(); }); var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close"); $cbutton.click(function() { - self.stop(); + self.check_exit(); + }); + }); + this.dataset.on_create.add(function(data) { + self.options.create_function(data).then(function(r) { + self.created_elements.push(r.result); + if (self._created) { + self._created.resolve(); + } }); }); - this.dataset.on_create.add(this.on_create); this.view_form.do_show(); + }, + check_exit: function() { + if (this.created_elements.length > 0) { + this.on_select_elements(this.created_elements); + } + this.stop(); } }); @@ -2076,6 +2120,7 @@ openerp.base.form.SelectCreateListView = openerp.base.ListView.extend({ }, select_record: function(index) { this.popup.on_select_elements([this.dataset.ids[index]]); + this.popup.stop(); }, do_select: function(ids, records) { this._super(ids, records); @@ -2199,8 +2244,8 @@ openerp.base.form.FieldBinary = openerp.base.form.Field.extend({ delete(window[this.iframe]); if (size === false) { this.notification.warn("File Upload", "There was a problem while uploading your file"); - // TODO: use openerp web exception handler - console.log("Error while uploading file : ", name); + // TODO: use openerp web crashmanager + this.log("Error while uploading file : ", name); } else { this.on_file_uploaded_and_valid.apply(this, arguments); this.on_ui_change(); @@ -2309,10 +2354,8 @@ openerp.base.form.widgets = new openerp.base.Registry({ 'url' : 'openerp.base.form.FieldUrl', 'text' : 'openerp.base.form.FieldText', 'text_wiki' : 'openerp.base.form.FieldText', -// 'date' : 'openerp.base.form.FieldDate', -// 'datetime' : 'openerp.base.form.FieldDatetime', - 'date' : 'openerp.base.form.FieldChar', - 'datetime' : 'openerp.base.form.FieldChar', + 'date' : 'openerp.base.form.FieldDate', + 'datetime' : 'openerp.base.form.FieldDatetime', 'selection' : 'openerp.base.form.FieldSelection', 'many2one' : 'openerp.base.form.FieldMany2One', 'many2many' : 'openerp.base.form.FieldMany2Many', diff --git a/addons/base/static/src/js/formats.js b/addons/base/static/src/js/formats.js new file mode 100644 index 00000000000..074cabe5eee --- /dev/null +++ b/addons/base/static/src/js/formats.js @@ -0,0 +1,208 @@ + +openerp.base.formats = function(openerp) { + +/** + * Converts a string to a Date javascript object using OpenERP's + * datetime string format (exemple: '2011-12-01 15:12:35'). + * + * The timezone is assumed to be UTC (standard for OpenERP 6.1) + * and will be converted to the browser's timezone. + * + * @param {String} str A string representing a datetime. + * @returns {Date} + */ +openerp.base.parse_datetime = function(str) { + if(!str) { + return str; + } + var regex = /\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/; + var res = regex.exec(str); + if ( res[0] != str ) { + throw "'" + str + "' is not a valid datetime"; + } + var obj = Date.parse(str + " GMT"); + if (! obj) { + throw "'" + str + "' is not a valid datetime"; + } + return obj; +}; + +/** + * Converts a string to a Date javascript object using OpenERP's + * date string format (exemple: '2011-12-01'). + * + * @param {String} str A string representing a date. + * @returns {Date} + */ +openerp.base.parse_date = function(str) { + if(!str) { + return str; + } + var regex = /\d\d\d\d-\d\d-\d\d/; + var res = regex.exec(str); + if ( res[0] != str ) { + throw "'" + str + "' is not a valid date"; + } + var obj = Date.parse(str); + if (! obj) { + throw "'" + str + "' is not a valid date"; + } + return obj; +}; + +/** + * Converts a string to a Date javascript object using OpenERP's + * time string format (exemple: '15:12:35'). + * + * @param {String} str A string representing a time. + * @returns {Date} + */ +openerp.base.parse_time = function(str) { + if(!str) { + return str; + } + var regex = /\d\d:\d\d:\d\d/; + var res = regex.exec(str); + if ( res[0] != str ) { + throw "'" + str + "' is not a valid time"; + } + var obj = Date.parse(str); + if (! obj) { + throw "'" + str + "' is not a valid time"; + } + return obj; +}; + +/* + * 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 + */ +var zpad = function(str, size) { + str = "" + str; + return new Array(size - str.length + 1).join('0') + str; +}; + +/** + * Converts a Date javascript object to a string using OpenERP's + * datetime string format (exemple: '2011-12-01 15:12:35'). + * + * The timezone 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. + */ +openerp.base.format_datetime = function(obj) { + if (!obj) { + return false; + } + return zpad(obj.getUTCFullYear(),4) + "-" + zpad(obj.getUTCMonth() + 1,2) + "-" + + zpad(obj.getUTCDate(),2) + " " + zpad(obj.getUTCHours(),2) + ":" + + zpad(obj.getUTCMinutes(),2) + ":" + zpad(obj.getUTCSeconds(),2); +}; + +/** + * Converts a Date javascript object to a string using OpenERP's + * date string format (exemple: '2011-12-01'). + * + * @param {Date} obj + * @returns {String} A string representing a date. + */ +openerp.base.format_date = function(obj) { + if (!obj) { + return false; + } + return zpad(obj.getFullYear(),4) + "-" + zpad(obj.getMonth() + 1,2) + "-" + + zpad(obj.getDate(),2); +}; + +/** + * Converts a Date javascript object to a string using OpenERP's + * time string format (exemple: '15:12:35'). + * + * @param {Date} obj + * @returns {String} A string representing a time. + */ +openerp.base.format_time = function(obj) { + if (!obj) { + return false; + } + return zpad(obj.getHours(),2) + ":" + zpad(obj.getMinutes(),2) + ":" + + zpad(obj.getSeconds(),2); +}; + +/** + * 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 + */ +openerp.base.format_value = function (value, descriptor, value_if_empty) { + // If NaN value, display as with a `false` (empty cell) + if (typeof value === 'number' && isNaN(value)) { + value = false; + } + switch (value) { + case false: + case Infinity: + case -Infinity: + return value_if_empty === undefined ? '' : value_if_empty; + } + switch (descriptor.widget || descriptor.type) { + case 'integer': + return _.sprintf('%d', value); + case 'float': + var precision = descriptor.digits ? descriptor.digits[1] : 2; + return _.sprintf('%.' + precision + 'f', value); + case 'float_time': + return _.sprintf("%02d:%02d", + Math.floor(value), + Math.round((value % 1) * 60)); + case 'progressbar': + return _.sprintf( + '<progress value="%.2f" max="100.0">%.2f%%</progress>', + value, value); + case 'many2one': + // name_get value format + return value[1]; + default: + return value; + } +}; + +/** + * Formats a provided cell based on its field type + * + * @param {Object} row_data record whose values should be displayed in the cell + * @param {Object} column column descriptor + * @param {"button"|"field"} column.tag base control type + * @param {String} column.type widget type for a field control + * @param {String} [column.string] button label + * @param {String} [column.icon] button icon + * @param {String} [value_if_empty=''] what to display if the field's value is ``false`` + */ +openerp.base.format_cell = function (row_data, column, value_if_empty) { + var attrs = column.modifiers_for(row_data); + if (attrs.invisible) { return ''; } + if (column.tag === 'button') { + return [ + '<button type="button" title="', column.string || '', '">', + '<img src="/base/static/src/img/icons/', column.icon, '.png"', + ' alt="', column.string || '', '"/>', + '</button>' + ].join('') + } + + return openerp.base.format_value( + row_data[column.id].value, column, value_if_empty); +} + +}; diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 04336e163c3..a0440221520 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -1,63 +1,5 @@ openerp.base.list = function (openerp) { openerp.base.views.add('list', 'openerp.base.ListView'); -openerp.base.list = { - /** - * Formats the rendring of a given value based on its field type - * - * @param {Object} row_data record whose values should be displayed in the cell - * @param {Object} column column descriptor - * @param {"button"|"field"} column.tag base control type - * @param {String} column.type widget type for a field control - * @param {String} [column.string] button label - * @param {String} [column.icon] button icon - * @param {String} [value_if_empty=''] what to display if the field's value is ``false`` - */ - render_cell: function (row_data, column, value_if_empty) { - var attrs = column.modifiers_for(row_data); - if (attrs.invisible) { return ''; } - if (column.tag === 'button') { - return [ - '<button type="button" title="', column.string || '', '">', - '<img src="/base/static/src/img/icons/', column.icon, '.png"', - ' alt="', column.string || '', '"/>', - '</button>' - ].join('') - } - - var value = row_data[column.id].value; - - // If NaN value, display as with a `false` (empty cell) - if (typeof value === 'number' && isNaN(value)) { - value = false; - } - switch (value) { - case false: - case Infinity: - case -Infinity: - return value_if_empty === undefined ? '' : value_if_empty; - } - switch (column.widget || column.type) { - case 'integer': - return _.sprintf('%d', value); - case 'float': - var precision = column.digits ? column.digits[1] : 2; - return _.sprintf('%.' + precision + 'f', value); - case 'float_time': - return _.sprintf("%02d:%02d", - Math.floor(value), - Math.round((value % 1) * 60)); - case 'progressbar': - return _.sprintf( - '<progress value="%.2f" max="100.0">%.2f%%</progress>', - value, value); - case 'many2one': - // name_get value format - return value[1]; - default: - return value; - } - } -}; openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ { defaults: { // records can be selected one by one @@ -85,8 +27,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * the default behaviors and possible options for the list view. * * @constructs - * @param view_manager - * @param session An OpenERP session object + * @param parent parent object * @param element_id the id of the DOM elements this view should link itself to * @param {openerp.base.DataSet} dataset the dataset the view should work with * @param {String} view_id the listview's identifier, if any @@ -102,16 +43,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi */ init: function(parent, element_id, dataset, view_id, options) { this._super(parent, element_id); - this.set_default_options(); - this.view_manager = parent || new openerp.base.NullViewManager(); + this.set_default_options(_.extend({}, this.defaults, options || {})); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; this.columns = []; - this.options = _.extend({}, this.defaults, options || {}); - this.set_groups(new openerp.base.ListView.Groups(this)); if (this.dataset instanceof openerp.base.DataSetStatic) { @@ -123,7 +61,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi /** * Retrieves the view's number of records per page (|| section) * - * options > defaults > view_manager.action.limit > indefinite + * options > defaults > parent.action.limit > indefinite * * @returns {Number|null} */ @@ -131,7 +69,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi if (this._limit === undefined) { this._limit = (this.options.limit || this.defaults.limit - || (this.view_manager.action || {}).limit + || (this.widget_parent.action || {}).limit || null); } return this._limit; @@ -157,7 +95,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi self.do_delete(ids); }, 'action': function (e, action_name, id, callback) { - self.do_action(action_name, id, callback); + self.do_button_action(action_name, id, callback); }, 'row_link': function (e, id, dataset) { self.do_activate_record(dataset.index, id, dataset); @@ -267,7 +205,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi }) .val(self._limit || 'NaN'); }); - if (this.options.sidebar && this.options.sidebar_id) { + if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) { this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id); this.sidebar.start(); this.sidebar.add_toolbar(data.fields_view.toolbar); @@ -389,9 +327,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi view = view || 'form'; this.dataset.index = index; _.delay(_.bind(function () { - if(this.view_manager) { - this.view_manager.on_mode_switch(view); - } + this.do_switch_view(view); }, this)); }, do_show: function () { @@ -416,8 +352,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * Reloads the list view based on the current settings (dataset & al) * * @param {Boolean} [grouped] Should the list be displayed grouped + * @param {Object} [context] context to send the server while loading the view */ - reload_view: function (grouped) { + reload_view: function (grouped, context) { var self = this; var callback = function (field_view_get) { self.on_loaded(field_view_get, grouped); @@ -428,7 +365,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi return this.rpc('/base/listview/load', { model: this.model, view_id: this.view_id, - context: this.dataset.get_context(), + context: this.dataset.get_context(context), toolbar: this.options.sidebar }, callback); } @@ -464,11 +401,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Object} results results of evaluating domain and process for a search */ do_actual_search: function (results) { - this.dataset.context = results.context; - this.dataset.domain = results.domain; this.groups.datagroup = new openerp.base.DataGroup( this, this.model, - results.domain, results.context, + this.dataset.get_domain(results.domain), + this.dataset.get_context(results.context), results.group_by); this.groups.datagroup.sort = this.dataset._sort; @@ -476,7 +412,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi results.group_by = null; } - this.reload_view(!!results.group_by).then( + this.reload_view(!!results.group_by, results.context).then( $.proxy(this, 'reload_content')); }, /** @@ -519,17 +455,16 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Object} id id of the record the action should be called on * @param {Function} callback should be called after the action is executed, if non-null */ - do_action: function (name, id, callback) { + do_button_action: function (name, id, callback) { var self = this, action = _.detect(this.columns, function (field) { return field.name === name; }); if (!action) { return; } - this.execute_action( - action, this.dataset, this.session.action_manager, id, function () { - $.when(callback.apply(this, arguments).then(function () { - self.compute_aggregates(); - })); + this.execute_action(action, this.dataset, id, function () { + $.when(callback.apply(this, arguments).then(function () { + self.compute_aggregates(); + })); }); }, /** @@ -541,11 +476,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi */ do_activate_record: function (index, id, dataset) { var self = this; - _.extend(this.dataset, { - domain: dataset.domain, - context: dataset.get_context() - }).read_slice([], 0, false, function () { - self.select_record(index); + this.dataset.read_slice({ + context: dataset.get_context(), + domain: dataset.get_domain() + }, function () { + self.select_record(index); }); }, /** @@ -644,7 +579,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } $footer_cells.filter(_.sprintf('[data-field=%s]', column.id)) - .html(openerp.base.list.render_cell(aggregation, column)); + .html(openerp.base.format_cell(aggregation, column)); }); } // TODO: implement reorder (drag and drop rows) @@ -732,7 +667,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. this.$current = this.$_element.clone(true); this.$current.empty().append( QWeb.render('ListView.rows', _.extend({ - render_cell: openerp.base.list.render_cell}, this))); + render_cell: openerp.base.format_cell}, this))); }, /** * Gets the ids of all currently selected records, if any @@ -837,7 +772,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. var old_index = this.dataset.index; this.dataset.index = record_index; read_p = this.dataset.read_index( - _.filter(_.pluck(this.columns, 'name'), _.identity), + _.pluck(_(this.columns).filter(function (r) {return r.tag === 'field';}), 'name'), function (record) { var form_record = self.transform_record(record); self.rows.splice(record_index, 1, form_record); @@ -863,7 +798,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. row: this.rows[record_index], row_parity: (record_index % 2 === 0) ? 'even' : 'odd', row_index: record_index, - render_cell: openerp.base.list.render_cell + render_cell: openerp.base.format_cell }); }, /** @@ -1023,7 +958,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas row_data[group.grouped_on] = group; var group_column = _(self.columns).detect(function (column) { return column.id === group.grouped_on; }); - $group_column.html(openerp.base.list.render_cell( + $group_column.html(openerp.base.format_cell( row_data, group_column, "Undefined" )); if (group.openable) { @@ -1099,10 +1034,11 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas d = new $.Deferred(), page = this.datagroup.openable ? this.page : view.page; - dataset.read_slice( - _.filter(_.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name'), _.identity), - page * limit, limit, - function (records) { + dataset.read_slice({ + fields: _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name'), + offset: page * limit, + limit: limit + }, function (records) { if (!self.datagroup.openable) { view.configure_pager(dataset); } else { diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index 96781b46307..a680c84a3fb 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -3,7 +3,6 @@ openerp.base.search = function(openerp) { openerp.base.SearchView = openerp.base.Widget.extend({ init: function(parent, element_id, dataset, view_id, defaults) { this._super(parent, element_id); - this.view_manager = parent || new openerp.base.NullViewManager(); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -361,9 +360,9 @@ openerp.base.search.fields = new openerp.base.Registry({ 'selection': 'openerp.base.search.SelectionField', 'datetime': 'openerp.base.search.DateTimeField', 'date': 'openerp.base.search.DateField', - 'one2many': 'openerp.base.search.OneToManyField', 'many2one': 'openerp.base.search.ManyToOneField', - 'many2many': 'openerp.base.search.ManyToManyField' + 'many2many': 'openerp.base.search.CharField', + 'one2many': 'openerp.base.search.CharField' }); openerp.base.search.Invalid = openerp.base.Class.extend( /** @lends openerp.base.search.Invalid# */{ /** @@ -622,39 +621,6 @@ openerp.base.search.CharField = openerp.base.search.Field.extend( /** @lends ope return this.$element.val(); } }); -openerp.base.search.BooleanField = openerp.base.search.Field.extend({ - template: 'SearchView.field.selection', - init: function () { - this._super.apply(this, arguments); - this.attrs.selection = [ - ['true', 'Yes'], - ['false', 'No'] - ]; - }, - /** - * Search defaults likely to be boolean values (for a boolean field). - * - * In the HTML, we only get strings, and our strings here are - * <code>'true'</code> and <code>'false'</code>, so ensure we get only - * those by truth-testing the default value. - * - * @param {Object} defaults default values for this search view - */ - render: function (defaults) { - var name = this.attrs.name; - if (name in defaults) { - defaults[name] = defaults[name] ? "true" : "false"; - } - return this._super(defaults); - }, - get_value: function () { - switch (this.$element.val()) { - case 'false': return false; - case 'true': return true; - default: return null; - } - } -}); openerp.base.search.NumberField = openerp.base.search.Field.extend(/** @lends openerp.base.search.NumberField# */{ get_value: function () { if (!this.$element.val()) { @@ -691,93 +657,73 @@ openerp.base.search.FloatField = openerp.base.search.NumberField.extend(/** @len return parseFloat(value); } }); -openerp.base.search.SelectionField = openerp.base.search.Field.extend({ +/** + * @class + * @extends openerp.base.search.Field + */ +openerp.base.search.SelectionField = openerp.base.search.Field.extend(/** @lends openerp.base.search.SelectionField# */{ template: 'SearchView.field.selection', get_value: function () { return this.$element.val(); } }); -openerp.base.search.DateField = openerp.base.search.Field.extend( /** @lends openerp.base.search.DateField# */{ - template: 'SearchView.fields.date', +openerp.base.search.BooleanField = openerp.base.search.SelectionField.extend(/** @lends openerp.base.search.BooleanField# */{ /** - * enables date picker on the HTML widgets + * @constructs + * @extends openerp.base.search.BooleanField */ - start: function () { - this._super(); - this.$element.find('input').datepicker({ - dateFormat: 'yy-mm-dd' - }); - }, - stop: function () { - this.$element.find('input').datepicker('destroy'); + init: function () { + this._super.apply(this, arguments); + this.attrs.selection = [ + ['true', 'Yes'], + ['false', 'No'] + ]; }, /** - * Returns an object with two optional keys ``from`` and ``to`` providing - * the values for resp. the from and to sections of the date widget. + * Search defaults likely to be boolean values (for a boolean field). * - * If a key is absent, then the corresponding field was not filled. + * In the HTML, we only want/get strings, and our strings here are ``true`` + * and ``false``, so ensure we use precisely those by truth-testing the + * default value (iif there is one in the view's defaults). * - * @returns {Object} + * @param {Object} defaults default values for this search view + * @returns {String} rendered boolean field */ - get_values: function () { - var values_array = this.$element.find('input').serializeArray(); - - if (!values_array || !values_array[0]) { - throw new openerp.base.search.Invalid( - this.attrs.name, null, "widget not ready"); - } - var from = values_array[0].value, - to = values_array[1].value; - - var field_values = {}; - if (from) { - field_values.from = from; - } - if (to) { - field_values.to = to; + render: function (defaults) { + var name = this.attrs.name; + if (name in defaults) { + defaults[name] = defaults[name] ? "true" : "false"; } - return field_values; + return this._super(defaults); }, - get_context: function () { - var values = this.get_values(); - if (!this.attrs.context || _.isEmpty(values)) { - return null; + get_value: function () { + switch (this.$element.val()) { + case 'false': return false; + case 'true': return true; + default: return null; } - return _.extend( - {}, this.attrs.context, - {own_values: {self: values}}); + } +}); +openerp.base.search.DateField = openerp.base.search.Field.extend( /** @lends openerp.base.search.DateField# */{ + /** + * enables date picker on the HTML widgets + */ + start: function () { + this._super(); + this.$element.addClass('field_date').datepicker({ + dateFormat: 'yy-mm-dd' + }); }, - get_domain: function () { - var values = this.get_values(); - if (_.isEmpty(values)) { - return null; - } - var domain = this.attrs['filter_domain']; - if (!domain) { - domain = []; - if (values.from) { - domain.push([this.attrs.name, '>=', values.from]); - } - if (values.to) { - domain.push([this.attrs.name, '<=', values.to]); - } - return domain; - } - - return _.extend( - {}, domain, - {own_values: {self: values}}); + stop: function () { + this.$element.datepicker('destroy'); + }, + get_value: function () { + return this.$element.val(); } }); openerp.base.search.DateTimeField = openerp.base.search.DateField.extend({ - // TODO: time? -}); -openerp.base.search.OneToManyField = openerp.base.search.CharField.extend({ - // TODO: .relation, .context, .domain }); openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ - // TODO: @widget - // TODO: .selection, .context, .domain init: function (view_section, field, view) { this._super(view_section, field, view); var self = this; @@ -845,9 +791,6 @@ openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ return this._super(); } }); -openerp.base.search.ManyToManyField = openerp.base.search.CharField.extend({ - // TODO: .related_columns (Array), .context, .domain -}); openerp.base.search.ExtendedSearch = openerp.base.OldWidget.extend({ template: 'SearchView.extended_search', @@ -930,11 +873,10 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({ this._super(); var _this = this; this.add_prop(); - this.$element.find('.searchview_extended_add_proposition').click(function (e) { + this.$element.find('.searchview_extended_add_proposition').click(function () { _this.add_prop(); }); - var delete_btn = this.$element.find('.searchview_extended_delete_group'); - delete_btn.click(function (e) { + this.$element.find('.searchview_extended_delete_group').click(function () { _this.stop(); }); }, @@ -944,7 +886,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({ }).compact().value(); var choice = this.$element.find(".searchview_extended_group_choice").val(); var op = choice == "all" ? "&" : "|"; - return [].concat(choice == "none" ? ['!'] : [], + return choice == "none" ? ['!'] : [].concat( _.map(_.range(_.max([0,props.length - 1])), function() { return op; }), props); }, @@ -956,10 +898,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({ parent.check_last_element(); }, set_last_group: function(is_last) { - if(is_last) - this.$element.addClass("last_group"); - else - this.$element.removeClass("last_group"); + this.$element.toggleClass('last_group', is_last); } }); @@ -982,8 +921,7 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.OldWidget.extend({ this.$element.find(".searchview_extended_prop_field").change(function() { _this.changed(); }); - var delete_btn = this.$element.find('.searchview_extended_delete_prop'); - delete_btn.click(function (e) { + this.$element.find('.searchview_extended_delete_prop').click(function () { _this.stop(); }); }, diff --git a/addons/base/static/src/js/view_tree.js b/addons/base/static/src/js/view_tree.js index 90972f84973..5c3c7457e1c 100644 --- a/addons/base/static/src/js/view_tree.js +++ b/addons/base/static/src/js/view_tree.js @@ -5,22 +5,202 @@ openerp.base.view_tree = function(openerp) { openerp.base.views.add('tree', 'openerp.base.TreeView'); -openerp.base.TreeView = openerp.base.Widget.extend({ /** * Genuine tree view (the one displayed as a tree, not the list) */ +openerp.base.TreeView = openerp.base.View.extend({ + /** + * Indicates that this view is not searchable, and thus that no search + * view should be displayed (if there is one active). + */ + searchable : false, + + init: function(parent, element_id, dataset, view_id, options) { + this._super(parent, element_id); + this.dataset = dataset; + this.model = dataset.model; + this.view_id = view_id; + + this.records = {}; + + this.options = _.extend({}, this.defaults, options || {}); + }, + start: function () { this._super(); - this.$element.append('Tree view'); + return this.rpc("/base/treeview/load", { + model: this.model, + view_id: this.view_id, + toolbar: this.view_manager ? !!this.view_manager.sidebar : false + }, this.on_loaded); + }, + /** + * Returns the list of fields needed to correctly read objects. + * + * Gathers the names of all fields in fields_view_get, and adds the + * field_parent (children_field in the tree view) if it's not already one + * of the fields to fetch + * + * @returns {Array} an array of fields which can be provided to DataSet.read_slice and others + */ + fields_list: function () { + var fields = _.keys(this.fields); + if (!_(fields).contains(this.children_field)) { + fields.push(this.children_field); + } + return fields; + }, + on_loaded: function (fields_view) { + var self = this; + var has_toolbar = !!fields_view.arch.attrs.toolbar; + // field name in OpenERP is kinda stupid: this is the name of the field + // holding the ids to the children of the current node, why call it + // field_parent? + this.children_field = fields_view['field_parent']; + this.fields_view = fields_view; + _(this.fields_view.arch.children).each(function (field) { + if (field.attrs.modifiers) { + field.attrs.modifiers = JSON.parse(field.attrs.modifiers); + } + }); + this.fields = fields_view.fields; + this.hook_row_click(); + this.$element.html(QWeb.render('TreeView', { + 'title': this.fields_view.arch.attrs.string, + 'fields_view': this.fields_view.arch.children, + 'fields': this.fields, + 'toolbar': has_toolbar + })); + + this.dataset.read_slice({fields: this.fields_list()}, function (records) { + if (!has_toolbar) { + // WARNING: will do a second read on the same ids, but only on + // first load so not very important + self.getdata(null, _(records).pluck('id')); + return; + } + + var $select = self.$element.find('select') + .change(function () { + var $option = $(this).find(':selected'); + self.getdata($option.val(), $option.data('children')); + }); + _(records).each(function (record) { + self.records[record.id] = record; + $('<option>') + .val(record.id) + .text(record.name) + .data('children', record[self.children_field]) + .appendTo($select); + }); + + if (!_.isEmpty(records)) { + $select.change(); + } + }); + }, + /** + * Sets up opening a row + */ + hook_row_click: function () { + var self = this; + this.$element.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) { + e.stopImmediatePropagation(); + self.activate($(this).closest('tr').data('id')); + }); + + this.$element.delegate('.treeview-tr', 'click', function () { + var is_loaded = 0, + $this = $(this), + record_id = $this.data('id'), + record = self.records[record_id], + children_ids = record[self.children_field]; + + _(children_ids).each(function(childid) { + if (self.$element.find('#treerow_' + childid).length) { + if (self.$element.find('#treerow_' + childid).is(':hidden')) { + is_loaded = -1; + } else { + is_loaded++; + } + } + }); + if (is_loaded === 0) { + if (!$this.parent().hasClass('oe-open')) { + self.getdata(record_id, children_ids); + } + } else { + self.showcontent(record_id, is_loaded < 0); + } + }); }, + // get child data of selected value + getdata: function (id, children_ids) { + var self = this; + + self.dataset.read_ids(children_ids, this.fields_list(), function (records) { + _(records).each(function (record) { + self.records[record.id] = record; + }); + + var $curr_node = self.$element.find('#treerow_' + id); + var children_rows = QWeb.render('TreeView.rows', { + 'records': records, + 'children_field': self.children_field, + 'fields_view': self.fields_view.arch.children, + 'fields': self.fields, + 'level': $curr_node.data('level') || 0, + 'render': openerp.base.format_value + }); + + if ($curr_node.length) { + $curr_node.addClass('oe-open'); + $curr_node.after(children_rows); + } else { + self.$element.find('tbody').html(children_rows); + } + }); + }, + + // Get details in listview + activate: function(id) { + var self = this; + this.rpc('/base/treeview/action', { + id: id, + model: this.dataset.model, + context: new openerp.base.CompoundContext( + this.dataset.get_context(), { + active_model: this.dataset.model, + active_id: id, + active_ids: [id]}) + }, function (actions) { + if (!actions.length) { return; } + var action = actions[0][2]; + self.do_action(action); + }); + }, + + // show & hide the contents + showcontent: function (record_id, show) { + this.$element.find('#treerow_' + record_id) + .toggleClass('oe-open', show); + + _(this.records[record_id][this.children_field]).each(function (child_id) { + var $child_row = this.$element.find('#treerow_' + child_id); + if ($child_row.hasClass('oe-open')) { + this.showcontent(child_id, false); + } + $child_row.toggle(show); + }, this); + }, + do_show: function () { this.$element.show(); }, + do_hide: function () { this.$element.hide(); + this.hidden = true; } }); - }; - -// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 9d299f23791..f21c6387d32 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -4,72 +4,83 @@ openerp.base.views = function(openerp) { +/** + * Registry for all the client actions key: tag value: widget + */ openerp.base.client_actions = new openerp.base.Registry(); + openerp.base.ActionManager = openerp.base.Widget.extend({ -// process all kind of actions - init: function(parent, element_id) { - this._super(parent, element_id); - this.viewmanager = null; - this.current_dialog = null; - // Temporary linking view_manager to session. - // Will use parent to find it when implementation will be done. - this.session.action_manager = this; + identifier_prefix: "actionmanager", + init: function(parent) { + this._super(parent); + this.inner_viewmanager = null; + this.dialog = null; + this.dialog_viewmanager = null; + this.client_widget = null; + }, + render: function() { + return "<div id='"+this.element_id+"'></div>"; + }, + dialog_stop: function () { + if (this.dialog) { + this.dialog_viewmanager.stop(); + this.dialog_viewmanager = null; + this.dialog.stop(); + this.dialog = null; + } + }, + inner_stop: function () { + if (this.inner_viewmanager) { + this.inner_viewmanager.stop(); + this.inner_viewmanager = null; + } }, - do_action: function(action, on_closed) { + do_action: function(action, on_close) { + var type = action.type.replace(/\./g,'_'); + var popup = action.target === 'new'; action.flags = _.extend({ - sidebar : action.target != 'new', - search_view : action.target != 'new', - new_window : false, - views_switcher : action.target != 'new', - action_buttons : action.target != 'new', - pager : action.target != 'new' + views_switcher : !popup, + search_view : !popup, + action_buttons : !popup, + sidebar : !popup, + pager : !popup }, action.flags || {}); - // instantiate the right controllers by understanding the action - if (!(action.type in this)) { - console.log("Action manager can't handle action of type " + action.type, action); + if (!(type in this)) { + this.log("Action manager can't handle action of type " + action.type, action); return; } - this[action.type](action, on_closed); - }, - - 'ir.actions.act_window': function (action, on_closed) { - if (!action.target && this.current_dialog) { - action.flags.new_window = true; - } - if (action.target == 'new') { - var dialog = this.current_dialog = new openerp.base.ActionDialog(this, { - title: action.name, - width: '50%' - }); - if (on_closed) { - dialog.close_callback = on_closed; + this[type](action, on_close); + }, + ir_actions_act_window: function (action, on_close) { + if (action.target === 'new') { + if (this.dialog == null) { + this.dialog = new openerp.base.Dialog(this, { title: action.name, width: '80%' }); + if(on_close) + this.dialog.on_close.add(on_close); + this.dialog.start(); + } else { + this.dialog_viewmanager.stop(); } - dialog.start(false); - var viewmanager = dialog.viewmanager = new openerp.base.ViewManagerAction(this, dialog.element_id, action); - viewmanager.start(); - dialog.open(); - } else if (action.flags.new_window) { - action.flags.new_window = false; + this.dialog_viewmanager = new openerp.base.ViewManagerAction(this, action); + this.dialog_viewmanager.appendTo(this.dialog.$element); + this.dialog.open(); + } else { + this.dialog_stop(); + this.inner_stop(); + this.inner_viewmanager = new openerp.base.ViewManagerAction(this, action); + this.inner_viewmanager.appendTo(this.$element); + } + /* new window code this.rpc("/base/session/save_session_action", { the_action : action}, function(key) { - var url = window.location.protocol + "//" + window.location.host + - window.location.pathname + "?" + jQuery.param({ s_action : "" + key }); - window.open(url); - if (on_closed) { - on_closed(); - } + var url = window.location.protocol + "//" + window.location.host + window.location.pathname + "?" + jQuery.param({ s_action : "" + key }); + window.open(url,'_blank'); }); - } else { - if (this.viewmanager) { - this.viewmanager.stop(); - } - this.viewmanager = new openerp.base.ViewManagerAction(this, this.element_id, action); - this.viewmanager.start(); - } + */ }, - 'ir.actions.act_window_close': function (action, on_closed) { - this.close_dialog(); + ir_actions_act_window_close: function (action, on_closed) { + this.dialog_stop(); }, - 'ir.actions.server': function (action, on_closed) { + ir_actions_server: function (action, on_closed) { var self = this; this.rpc('/base/action/run', { action_id: action.id, @@ -78,47 +89,28 @@ openerp.base.ActionManager = openerp.base.Widget.extend({ self.do_action(action, on_closed) }); }, - 'ir.actions.client': function (action) { - var Handler = openerp.base.client_actions.get_object(action.tag); - new Handler(this, this.element_id, action.params).start(); - }, - close_dialog: function () { - if (this.current_dialog) { - this.current_dialog.stop(); - this.current_dialog = null; - } - } -}); - -openerp.base.ActionDialog = openerp.base.Dialog.extend({ - identifier_prefix: 'action_dialog', - on_close: function() { - this._super(this, arguments); - if (this.close_callback) { - this.close_callback(); - } - }, - stop: function() { - this._super(this, arguments); - if (this.viewmanager) { - this.viewmanager.stop(); - } + ir_actions_client: function (action) { + this.client_widget = openerp.base.client_actions.get_object(action.tag); + new this.client_widget(this, this.element_id, action.params).start(); } }); openerp.base.ViewManager = openerp.base.Widget.extend({ - init: function(parent, element_id, dataset, views) { - this._super(parent, element_id); + identifier_prefix: "viewmanager", + init: function(parent, dataset, views) { + this._super(parent); this.model = dataset.model; this.dataset = dataset; this.searchview = null; this.active_view = null; - this.views_src = _.map(views, function(x) - {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;}); + this.views_src = _.map(views, function(x) {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;}); this.views = {}; this.flags = this.flags || {}; this.registry = openerp.base.views; }, + render: function() { + return QWeb.render("ViewManager", {"prefix": this.element_id, views: this.views_src}) + }, /** * @returns {jQuery.Deferred} initial view loading promise */ @@ -126,28 +118,26 @@ openerp.base.ViewManager = openerp.base.Widget.extend({ this._super(); var self = this; this.dataset.start(); - - this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: this.views_src})); this.$element.find('.oe_vm_switch button').click(function() { self.on_mode_switch($(this).data('view-type')); }); - + var views_ids = {}; _.each(this.views_src, function(view) { self.views[view.view_type] = $.extend({}, view, { controller : null, options : _.extend({ - sidebar_id : self.element_id + '_sidebar_' + view.view_type - }, self.flags) + sidebar_id : self.element_id + '_sidebar_' + view.view_type, + action : self.action, + action_views_ids : views_ids + }, self.flags, view.options || {}) }); + views_ids[view.view_type] = view.view_id; }); if (this.flags.views_switcher === false) { this.$element.find('.oe_vm_switch').hide(); } // switch to the first one in sequence return this.on_mode_switch(this.views_src[0].view_type); - - }, - stop: function() { }, /** * Asks the view manager to switch visualization mode. @@ -168,7 +158,8 @@ openerp.base.ViewManager = openerp.base.Widget.extend({ if (view.embedded_view) { controller.set_embedded_view(view.embedded_view); } - if (view_type === 'list' && this.flags.search_view === false && this.action && this.action['auto_search']) { + controller.do_switch_view.add_last(this.on_mode_switch); + if (view_type === 'list' && this.flags.search_view === false && this.action && this.action['auto_search']) { // In case the search view is not instantiated: manually call ListView#search var domains = !_(self.action.domain).isEmpty() ? [self.action.domain] : [], @@ -217,11 +208,12 @@ openerp.base.ViewManager = openerp.base.Widget.extend({ }, /** * Event launched when a controller has been inited. - * + * * @param {String} view_type type of view * @param {String} view the inited controller */ - on_controller_inited: function(view_type, view) {}, + on_controller_inited: function(view_type, view) { + }, /** * Sets up the current viewmanager's search view. * @@ -238,9 +230,8 @@ openerp.base.ViewManager = openerp.base.Widget.extend({ this.searchview.hide(); } this.searchview.on_search.add(function(domains, contexts, groupbys) { - self.views[self.active_view].controller.do_search.call( - self, domains.concat(self.domains()), - contexts.concat(self.contexts()), groupbys); + var controller = self.views[self.active_view].controller; + controller.do_search.call(controller, domains, contexts, groupbys); }); return this.searchview.start(); }, @@ -254,50 +245,22 @@ openerp.base.ViewManager = openerp.base.Widget.extend({ on_remove: function() { }, on_edit: function() { - }, - /** - * Domains added on searches by the view manager, to override in subsequent - * view manager in order to add new pieces of domains to searches - * - * @returns an empty list - */ - domains: function () { - return []; - }, - /** - * Contexts added on searches by the view manager. - * - * @returns an empty list - */ - contexts: function () { - return []; - } -}); - -openerp.base.NullViewManager = openerp.base.generate_null_object_class(openerp.base.ViewManager, { - init: function(parent) { - this._super(parent); - if(parent) - this.session = parent.session; - this.action = {flags: {}}; } }); -// TODO Will move to action Manager openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ - init: function(parent, element_id, action) { + init: function(parent, action) { this.session = parent.session; + this.action = action; var dataset; if (!action.res_id) { - dataset = new openerp.base.DataSetSearch(this, action.res_model, action.context || null); + dataset = new openerp.base.DataSetSearch(this, action.res_model, action.context, action.domain); } else { - dataset = new openerp.base.DataSetStatic(this, action.res_model, {}, [action.res_id]); - if (action.context) { - // TODO fme: should normalize all DataSets constructors to (session, model, context, ...) - dataset.context = action.context; - } + this.action.flags.search_view = false; + dataset = new openerp.base.DataSetStatic(this, action.res_model, action.context, [action.res_id]); + dataset.index = 0; } - this._super(parent, element_id, dataset, action.views); + this._super(parent, dataset, action.views); this.action = action; this.flags = this.action.flags || {}; if (action.res_model == 'board.board' && action.views.length == 1 && action.views) { @@ -307,6 +270,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ }, start: function() { var inital_view_loaded = this._super(); + var search_defaults = {}; _.each(this.action.context, function (value, key) { var match = /^search_default_(.*)$/.exec(key); @@ -329,32 +293,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ } } }, - stop: function() { - // should be replaced by automatic destruction implemented in Widget - this._super(); - }, - /** - * adds action domain to the search domains - * - * @returns the action's domain - */ - domains: function () { - if (!this.action.domain) { - return []; - } - return [this.action.domain]; - }, - /** - * adds action context to the search contexts - * - * @returns the action's context - */ - contexts: function () { - if (!this.action.context) { - return []; - } - return [this.action.context]; - }, on_mode_switch: function (view_type) { this._super(view_type); this.shortcut_check(this.views[view_type]); @@ -394,8 +332,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ $shortcut_toggle.addClass("oe-shortcut-remove"); } }); - - } }); @@ -491,7 +427,9 @@ openerp.base.View = openerp.base.Widget.extend({ _.defaults(this.options, { // All possible views options should be defaulted here sidebar_id: null, - sidebar: true + sidebar: true, + action: null, + action_views_ids: {} }); }, /** @@ -503,17 +441,12 @@ openerp.base.View = openerp.base.Widget.extend({ * @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 {openerp.base.DataSet} dataset a dataset object used to communicate with the server - * @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched * @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) */ - execute_action: function (action_data, dataset, action_manager, record_id, on_closed) { + execute_action: function (action_data, dataset, record_id, on_closed) { var self = this; - if (action_manager.current_dialog) { - on_closed = action_manager.current_dialog.close_callback; - } var handler = function (r) { - action_manager.close_dialog(); var action = r.result; if (action && action.constructor == Object) { action.context = action.context || {}; @@ -522,38 +455,36 @@ openerp.base.View = openerp.base.Widget.extend({ active_ids: [record_id || false], active_model: dataset.model }); - action.flags = { - new_window: true - }; - action_manager.do_action(action, on_closed); + self.do_action(action, on_closed); } else if (on_closed) { on_closed(action); } }; - if (!action_data.special) { - var context = new openerp.base.CompoundContext(dataset.get_context(), action_data.context || {}); - switch(action_data.type) { - case 'object': - return dataset.call_button(action_data.name, [[record_id], context], handler); - case 'action': - return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10), context: context }, handler); - default: - return dataset.exec_workflow(record_id, action_data.name, handler); - } - } else { - action_manager.close_dialog(); - } + var context = new openerp.base.CompoundContext(dataset.get_context(), action_data.context || {}); + + if (action_data.special) { + handler({result: {"type":"ir.actions.act_window_close"}}); + } else if (action_data.type=="object") { + return dataset.call_button(action_data.name, [[record_id], context], handler); + } else if (action_data.type=="action") { + return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10), context: context }, handler); + } else { + return dataset.exec_workflow(record_id, action_data.name, handler); + } }, /** * Directly set a view to use instead of calling fields_view_get. This method must * be called before start(). When an embedded view is set, underlying implementations * of openerp.base.View must use the provided view instead of any other one. - * + * * @param embedded_view A view. */ set_embedded_view: function(embedded_view) { this.embedded_view = embedded_view; + this.options.sidebar = false; + }, + do_switch_view: function(view) { }, set_common_sidebar_sections: function(sidebar) { sidebar.add_section('customize', "Customize", [ @@ -598,16 +529,16 @@ openerp.base.View = openerp.base.Widget.extend({ } }, on_sidebar_edit_workflow: function() { - console.log('Todo'); + this.log('Todo'); }, on_sidebar_customize_object: function() { - console.log('Todo'); + this.log('Todo'); }, on_sidebar_import: function() { }, on_sidebar_export: function() { var export_view = new openerp.base.DataExport(this, this.dataset); - export_view.start(false); + export_view.start(); }, on_sidebar_translate: function() { }, diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 2111ba11c54..31d1309a6e3 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -42,7 +42,6 @@ </td> <td valign="top"> <div id="oe_app" class="oe-application"> - <div style="width: 100%;">&nbsp;</div> </div> </td> </tr> @@ -79,7 +78,7 @@ </table> <table align="center" class="db_option_table"> <tr> - <td><label for="super_admin_pwd">Super admin password:</label></td> + <td><label for="super_admin_pwd">Master password:</label></td> <td><input type="password" name="super_admin_pwd" class="required" autofocus="autofocus"/></td> </tr> @@ -104,7 +103,7 @@ </td> </tr> <tr> - <td><label for="create_admin_pwd">Super admin password:</label></td> + <td><label for="create_admin_pwd">Admin password:</label></td> <td><input type="password" name="create_admin_pwd" class="required"/></td> </tr> <tr> @@ -139,7 +138,7 @@ </td> </tr> <tr> - <td><label for="drop_password">Password:</label></td> + <td><label for="drop_password">Master Password:</label></td> <td><input type="password" name="drop_pwd" class="required"/></td> </tr> <tr> @@ -171,7 +170,7 @@ </td> </tr> <tr> - <td><label for="backup_pwd">Password:</label></td> + <td><label for="backup_pwd">Master Password:</label></td> <td><input type="password" name="backup_pwd" class="required"/></td> </tr> <tr> @@ -196,7 +195,7 @@ autofocus="autofocus"/></td> </tr> <tr> - <td><label for="restore_pwd">Password:</label></td> + <td><label for="restore_pwd">Master Password:</label></td> <td><input type="password" name="restore_pwd" class="required"/></td> </tr> <tr> @@ -214,23 +213,23 @@ <table width="100%"> <tr> <td class="option_string"> - CHANGE DATABASE PASSWORD + CHANGE MASTER PASSWORD </td> </tr> </table> <table align="center" class="db_option_table"> <tr> - <td><label for="old_pwd">Old password:</label></td> + <td><label for="old_pwd">Master password:</label></td> <td><input type="password" name="old_pwd" class="required" minlength="1" autofocus="autofocus"/></td> </tr> <tr> - <td><label for="new_pwd">New password:</label></td> + <td><label for="new_pwd">New master password:</label></td> <td><input type="password" name="new_pwd" class="required" minlength="1"/></td> </tr> <tr> - <td><label for="confirm_pwd">Confirm password:</label></td> + <td><label for="confirm_pwd">Confirm new master password:</label></td> <td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/></td> </tr> @@ -458,6 +457,41 @@ </ul> </div> </t> +<t t-name="TreeView"> + <h2 class="oe_view_title"><t t-esc="title"/></h2> + <select t-if="toolbar" style="width: 30%"> + </select> + <table class="oe-treeview-table"> + <thead> + <tr> + <th t-foreach="fields_view" t-as="field" + t-if="!field.attrs.modifiers.tree_invisible" + class="treeview-header"> + <t t-esc="fields[field.attrs.name].string" /> + </th> + </tr> + </thead> + <tbody> + </tbody> + </table> +</t> +<tr t-name="TreeView.rows" + t-foreach="records" t-as="record" + t-att-id="'treerow_' + record.id" + t-att-data-id="record.id" t-att-data-level="level + 1"> + <t t-set="children" t-value="record[children_field]"/> + <t t-set="has_children" t-value="children and children.length"/> + + <td t-foreach="fields_view" t-as="field" + t-if="!field.attrs.modifiers.tree_invisible" + t-att-data-id="record.id" + t-att-style="!field_index ? 'background-position: ' + 19*level + 'px; padding-left: ' + 19*level + 'px' : undefined" + t-att-class="!field_index and has_children ? 'treeview-tr' : 'treeview-td'"> + <span t-if="!field.attrs.modifiers.invisible"> + <t t-esc="render(record[field.attrs.name], fields[field.attrs.name])" /> + </span> + </td> +</tr> <table t-name="ListView" class="oe-listview-content"> <t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/> <thead class="ui-widget-header"> @@ -522,14 +556,9 @@ </tfoot> </table> <t t-name="ListView.rows" t-foreach="rows" t-as="row"> - <t t-call="ListView.row"> - <t t-set="style" t-value="null"/> - <t-if test="row.color"> - <t t-set="style" t-value="'color: ' + row.color"/> - </t-if> - </t> + <t t-call="ListView.row"/> </t> -<tr t-name="ListView.row" t-att-style="style" t-att-class="row_parity" +<tr t-name="ListView.row" t-att-class="row_parity" t-att-data-index="row_index"> <t t-foreach="columns" t-as="column"> <td t-if="column.meta"> @@ -943,22 +972,6 @@ <t t-if="filters.length" t-raw="filters.render(defaults)"/> </div> </t> -<t t-name="SearchView.fields.date"> - <label style="display: block" t-att-title="attrs.help" - t-att-for="element_id"> - <t t-esc="attrs.string || attrs.name"/> - <span t-if="attrs.help">(?)</span> - </label> - <div style="white-space: nowrap;" t-att-id="element_id"> - <input t-att-name="attrs.name" type="text" class="field_date" - t-att-value="defaults[attrs.name] || ''" - t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined"/> - to - <input t-att-name="attrs.name" type="text" class="field_date" - t-att-value="defaults[attrs.name] || ''"/> - <t t-if="filters.length" t-raw="filters.render(defaults)"/> - </div> -</t> <t t-name="SearchView.field.selection"> <label style="display: block" t-att-title="attrs.help" t-att-for="element_id"> @@ -1110,11 +1123,17 @@ </t> <t t-name="SelectCreatePopup.search.buttons"> <button type="button" class="oe_selectcreatepopup-search-select" disabled="disabled">Select</button> - <button type="button" class="oe_selectcreatepopup-search-close">Close</button> + <button type="button" class="oe_selectcreatepopup-search-close">Cancel</button> </t> <t t-name="SelectCreatePopup.form.buttons"> - <button type="button" class="oe_selectcreatepopup-form-save">Save</button> - <button type="button" class="oe_selectcreatepopup-form-close">Close</button> + <t t-if="widget.options.disable_multiple_selection"> + <button type="button" class="oe_selectcreatepopup-form-save">Save</button> + </t> + <t t-if="! widget.options.disable_multiple_selection"> + <button type="button" class="oe_selectcreatepopup-form-save-new">Save & New</button> + <button type="button" class="oe_selectcreatepopup-form-save">Save & Close</button> + </t> + <button type="button" class="oe_selectcreatepopup-form-close">Cancel</button> </t> <t t-name="FormOpenPopup"> <div t-att-id="element_id"> @@ -1123,7 +1142,7 @@ </t> <t t-name="FormOpenPopup.form.buttons"> <button type="button" class="oe_formopenpopup-form-save">Save</button> - <button type="button" class="oe_formopenpopup-form-close">Close</button> + <button type="button" class="oe_formopenpopup-form-close">Cancel</button> </t> <t t-name="ListView.row.frame" t-extend="WidgetFrame"> <t t-jquery="tr"> |
