diff --git a/.bzrignore b/.bzrignore index 6e9359571f4..b83a43bf1d4 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,12 +1,11 @@ -RE:lib/(?!populate.sh).* .* *.egg-info *.orig -bin/ build/ -dist/ -include/ +RE:^bin/ +RE:^dist/ +RE:^include/ -share/ -man/ -lib/ +RE:^share/ +RE:^man/ +RE:^lib/ diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py index 9cf8a6c893e..cfe8850ab03 100644 --- a/addons/base/__openerp__.py +++ b/addons/base/__openerp__.py @@ -22,7 +22,7 @@ "static/lib/underscore/underscore.string.js", "static/src/js/boot.js", "static/src/js/core.js", - "static/src/js/dates.js", + "static/src/js/formats.js", "static/src/js/chrome.js", "static/src/js/views.js", "static/src/js/data.js", diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 6526c81b4a8..3caa07c6197 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -18,6 +18,7 @@ import openerpweb import openerpweb.ast import openerpweb.nonliterals +from babel.messages.pofile import read_po # Should move to openerpweb.Xml2Json class Xml2Json: @@ -156,6 +157,43 @@ class WebClient(openerpweb.Controller): 'css': css } return r + + @openerpweb.jsonrequest + def translations(self, req, mods, lang): + lang_model = req.session.model('res.lang') + ids = lang_model.search([("code", "=", lang)]) + if ids: + lang_obj = lang_model.read(ids[0], ["direction", "date_format", "time_format", + "grouping", "decimal_point", "thousands_sep"]) + else: + lang_obj = None + + if lang.count("_") > 0: + separator = "_" + else: + separator = "@" + langs = lang.split(separator) + langs = [separator.join(langs[:x]) for x in range(1, len(langs) + 1)] + + transs = {} + for addon_name in mods: + transl = {"messages":[]} + transs[addon_name] = transl + for l in langs: + f_name = os.path.join(openerpweb.path_addons, addon_name, "po", l + ".po") + if not os.path.exists(f_name): + continue + try: + with open(f_name) as t_file: + po = read_po(t_file) + except: + continue + for x in po: + if x.id and x.string: + transl["messages"].append({'id': x.id, 'string': x.string}) + return {"modules": transs, + "lang_parameters": lang_obj} + class Database(openerpweb.Controller): _cp_path = "/base/database" @@ -251,10 +289,12 @@ class Session(openerpweb.Controller): @openerpweb.jsonrequest def login(self, req, db, login, password): req.session.login(db, login, password) + ctx = req.session.get_context() return { "session_id": req.session_id, "uid": req.session._uid, + "context": ctx } @openerpweb.jsonrequest @@ -390,10 +430,10 @@ def load_actions_from_ir_values(req, key, key2, models, meta, context): Values = req.session.model('ir.values') actions = Values.get(key, key2, models, meta, context) - return [(id, name, clean_action(action, req.session)) + return [(id, name, clean_action(action, req.session, context=context)) for id, name, action in actions] -def clean_action(action, session): +def clean_action(action, session, context=None): action.setdefault('flags', {}) if action['type'] != 'ir.actions.act_window': return action @@ -401,7 +441,7 @@ def clean_action(action, session): if isinstance(action.get('context'), basestring): action['context'] = eval( action['context'], - session.evaluation_context()) or {} + session.evaluation_context(context=context)) or {} if isinstance(action.get('domain'), basestring): action['domain'] = eval( @@ -559,6 +599,7 @@ class DataSet(openerpweb.Controller): :rtype: list """ Model = request.session.model(model) + context, domain = eval_context_and_domain( request.session, request.context, domain) @@ -581,9 +622,14 @@ class DataSet(openerpweb.Controller): } + @openerpweb.jsonrequest + def read(self, request, model, ids, fields=False): + return self.do_search_read(request, model, ids, fields) + @openerpweb.jsonrequest def get(self, request, model, ids, fields=False): return self.do_get(request, model, ids, fields) + def do_get(self, request, model, ids, fields=False): """ Fetches and returns the records of the model ``model`` whose ids are in ``ids``. @@ -668,6 +714,12 @@ class DataSet(openerpweb.Controller): Model = req.session.model(model) return Model.default_get(fields, req.session.eval_context(req.context)) + @openerpweb.jsonrequest + def name_search(self, req, model, search_str, domain=[], context={}): + m = req.session.model(model) + r = m.name_search(search_str+'%', domain, '=ilike', context) + return {'result': r} + class DataGroup(openerpweb.Controller): _cp_path = "/base/group" @openerpweb.jsonrequest @@ -760,8 +812,8 @@ class View(openerpweb.Controller): """ Parses an arbitrary string containing a domain, transforms it to either a literal domain or a :class:`openerpweb.nonliterals.Domain` - :param domain: the domain to parse, if the domain is not a string it is assumed to - be a literal domain and is returned as-is + :param domain: the domain to parse, if the domain is not a string it + is assumed to be a literal domain and is returned as-is :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ @@ -777,8 +829,8 @@ class View(openerpweb.Controller): """ Parses an arbitrary string containing a context, transforms it to either a literal context or a :class:`openerpweb.nonliterals.Context` - :param context: the context to parse, if the context is not a string it is assumed to - be a literal domain and is returned as-is + :param context: the context to parse, if the context is not a string it + is assumed to be a literal domain and is returned as-is :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ @@ -1001,6 +1053,19 @@ class Action(openerpweb.Controller): return clean_action(req.session.model('ir.actions.server').run( [action_id], req.session.eval_context(req.context)), req.session) +class TreeView(View): + _cp_path = "/base/treeview" + + @openerpweb.jsonrequest + def load(self, req, model, view_id, toolbar=False): + return self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar) + + @openerpweb.jsonrequest + def action(self, req, model, id): + return load_actions_from_ir_values( + req,'action', 'tree_but_open',[(model, id)], + False, req.session.eval_context(req.context)) + def export_csv(fields, result): fp = StringIO() writer = csv.writer(fp, quoting=csv.QUOTE_ALL) @@ -1213,4 +1278,3 @@ class Export(View): return export_xls(field, result) else: return export_csv(field, result) - diff --git a/addons/base/po/base.pot b/addons/base/po/base.pot new file mode 100644 index 00000000000..e6fc85cfa0a --- /dev/null +++ b/addons/base/po/base.pot @@ -0,0 +1,32 @@ +# Translations template for PROJECT. +# Copyright (C) 2011 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2011. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2011-08-16 15:47+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#: addons/base/static/src/js/form.js:1459 +msgid "   Search More..." +msgstr "" + +#: addons/base/static/src/js/form.js:1472 +#, python-format +msgid "   Create \"%s\"" +msgstr "" + +#: addons/base/static/src/js/form.js:1478 +msgid "   Create and Edit..." +msgstr "" + diff --git a/addons/base/po/fr_FR.po b/addons/base/po/fr_FR.po new file mode 100644 index 00000000000..08905cac036 --- /dev/null +++ b/addons/base/po/fr_FR.po @@ -0,0 +1,33 @@ +# Translations template for PROJECT. +# Copyright (C) 2011 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# niv , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2011-08-09 17:30+0200\n" +"PO-Revision-Date: 2011-08-09 17:31+0200\n" +"Last-Translator: niv \n" +"Language-Team: French\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.4\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: addons/base/static/src/js/form.js:1459 +msgid "   Search More..." +msgstr "   Chercher plus..." + +#: addons/base/static/src/js/form.js:1472 +#, python-format +msgid "   Create \"%s\"" +msgstr "   Créer \"%s\"" + +#: addons/base/static/src/js/form.js:1478 +msgid "   Create and Edit..." +msgstr "   Créer et éditer..." + diff --git a/addons/base/static/lib/qweb/qweb2.js b/addons/base/static/lib/qweb/qweb2.js index 7430464e2d8..89a77bf1b85 100644 --- a/addons/base/static/lib/qweb/qweb2.js +++ b/addons/base/static/lib/qweb/qweb2.js @@ -1,5 +1,7 @@ // TODO: trim support // TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650 +// TODO: templates orverwritten could be called by t-call="__super__" ? +// TODO: t-set + t-value + children node == scoped variable ? var QWeb2 = { expressions_cache: {}, reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object,Date'.split(','), @@ -21,6 +23,11 @@ var QWeb2 = { } throw new Error(prefix + ": " + message); }, + warning : function(message) { + if (typeof(window) !== 'undefined' && window.console) { + window.console.warn(message); + } + }, trim: function(s, mode) { switch (mode) { case "left": @@ -638,6 +645,9 @@ QWeb2.Element = (function() { compile_action_set : function(value) { var variable = this.format_expression(value); if (this.actions['value']) { + if (this.children.length) { + this.engine.tools.warning("@set with @value plus node chidren found. Children are ignored."); + } this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");"); this.process_children = false; } else { 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. will map to // openerp.sessions.sessionname. 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 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 = $('
').dialog(this.options); - if (auto_open !== false) { - this.open(); - } + start: function () { + this.$dialog = $('
').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= 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: "   Search More...", action: function() { + values.push({label: _t("   Search More..."), 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: '   Create "' + - $('').text(search_val).html() + '"', action: function() { + values.push({label: _.sprintf(_t('   Create "%s"'), + $('').text(search_val).html()), action: function() { self._quick_create(search_val); }}); } // create... - values.push({label: "   Create and Edit...", action: function() { + values.push({label: _t("   Create and Edit..."), 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( + '%.2f%%', + 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 [ + '' + ].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 [ - '' - ].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( - '%.2f%%', - 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 - * 'true' and 'false', 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.BooleanField = openerp.base.search.SelectionField.extend(/** @lends openerp.base.search.BooleanField# */{ + /** + * @constructs + * @extends openerp.base.search.BooleanField + */ + 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 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). + * + * @param {Object} defaults default values for this search view + * @returns {String} rendered boolean field + */ + 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.DateField = openerp.base.search.Field.extend( /** @lends openerp.base.search.DateField# */{ - template: 'SearchView.fields.date', /** * enables date picker on the HTML widgets */ start: function () { this._super(); - this.$element.find('input').datepicker({ + this.$element.addClass('field_date').datepicker({ dateFormat: 'yy-mm-dd' }); }, stop: function () { - this.$element.find('input').datepicker('destroy'); + this.$element.datepicker('destroy'); }, - /** - * Returns an object with two optional keys ``from`` and ``to`` providing - * the values for resp. the from and to sections of the date widget. - * - * If a key is absent, then the corresponding field was not filled. - * - * @returns {Object} - */ - 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; - } - return field_values; - }, - get_context: function () { - var values = this.get_values(); - if (!this.attrs.context || _.isEmpty(values)) { - return null; - } - return _.extend( - {}, this.attrs.context, - {own_values: {self: values}}); - }, - 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}}); + 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; + $('