diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 9d65acfb760..99fb1cd2440 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -88,6 +88,7 @@ class Session(openerpweb.Controller): @openerpweb.jsonrequest def login(self, req, db, login, password): req.session.login(db, login, password) + return { "session_id": req.session_id, "uid": req.session._uid, @@ -187,8 +188,38 @@ def load_actions_from_ir_values(req, key, key2, models, meta, context): action['domain'], req.session.evaluation_context( action['context'])) or [] + fix_view_modes(action) return actions +def fix_view_modes(action): + """ For historical reasons, OpenERP has weird dealings in relation to + view_mode and the view_type attribute (on window actions): + + * one of the view modes is ``tree``, which stands for both list views + and tree views + * the choice is made by checking ``view_type``, which is either + ``form`` for a list view or ``tree`` for an actual tree view + + This methods simply folds the view_type into view_mode by adding a + new view mode ``list`` which is the result of the ``tree`` view_mode + in conjunction with the ``form`` view_type. + + TODO: this should go into the doc, some kind of "peculiarities" section + + :param dict action: an action descriptor + :returns: nothing, the action is modified in place + """ + if action.pop('view_type') != 'form': + return + + action['view_mode'] = ','.join( + mode if mode != 'tree' else 'list' + for mode in action['view_mode'].split(',')) + action['views'] = [ + [id, mode if mode != 'tree' else 'list'] + for id, mode in action['views'] + ] + class Menu(openerpweb.Controller): _cp_path = "/base/menu" @@ -237,7 +268,6 @@ class Menu(openerpweb.Controller): return {"action": actions} - class DataSet(openerpweb.Controller): _cp_path = "/base/dataset" @@ -383,7 +413,7 @@ class View(openerpweb.Controller): :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ - domain = elem.get(attr_name) + domain = elem.get(attr_name, '').strip() if domain: try: elem.set( @@ -408,7 +438,7 @@ class View(openerpweb.Controller): """ self.parse_domain(elem, 'domain', session) self.parse_domain(elem, 'filter_domain', session) - context_string = elem.get('context') + context_string = elem.get('context', '').strip() if context_string: try: elem.set('context', diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 531941f85a9..151ec6e214e 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -395,6 +395,11 @@ body.openerp { padding-left: 10px; } +/* View Manager */ +.openerp .views-switchers { + text-align: right; +} + /* Form */ .openerp .required.error { border: 1px solid #900; @@ -413,6 +418,13 @@ body.openerp { border-radius: 3px; background: white; } +.openerp input.field_date { + background: url('../img/ui/field_calendar.png') no-repeat right center; + background-origin: content-box; + -moz-background-origin: content; + -moz-background-origin: content-box; + -webkit-background-origin: content-box; +} /* Widgets */ .openerp .separator { diff --git a/addons/base/static/src/img/ui/field_calendar.png b/addons/base/static/src/img/ui/field_calendar.png new file mode 100644 index 00000000000..99a1f3f9ef1 Binary files /dev/null and b/addons/base/static/src/img/ui/field_calendar.png differ diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 14577c9d0ab..523f2c8a2b7 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -184,8 +184,19 @@ openerp.base.BasicController = Class.extend( * Controller start * event binding, rpc and callback calling required to initialize the * object can happen here + * + * Returns a promise object letting callers (subclasses and direct callers) + * know when this component is done starting + * + * @returns {jQuery.Deferred} */ start: function() { + // returns an already fulfilled promise. Maybe we could return nothing? + // $.when can take non-deferred and in that case it simply considers + // them all as fulfilled promises. + // But in thise case we *have* to ensure callers use $.when and don't + // try to call deferred methods on this return value. + return $.Deferred().done().promise(); }, stop: function() { }, @@ -250,6 +261,19 @@ openerp.base.Session = openerp.base.BasicController.extend( } }); }, + /** + * 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) { // Construct a JSON-RPC2 request, method is currently unused params.session_id = this.session_id; @@ -259,17 +283,22 @@ openerp.base.Session = openerp.base.BasicController.extend( error_callback = typeof(error_callback) != "undefined" ? error_callback : this.on_rpc_error; // Call using the rpc_mode - this.rpc_ajax(url, { + return this.rpc_ajax(url, { jsonrpc: "2.0", method: "call", params: params, id:null }, success_callback, error_callback); }, + /** + * Raw JSON-RPC call + * + * @returns {jQuery.Deferred} ajax-based deferred object + */ rpc_ajax: function(url, payload, success_callback, error_callback) { var self = this; this.on_rpc_request(); - $.ajax({ + return $.ajax({ type: "POST", url: url, dataType: 'json', @@ -446,9 +475,18 @@ openerp.base.Controller = openerp.base.BasicController.extend( if(this.session) this.session.log.apply(this.session,arguments); }, + /** + * Performs a JSON-RPC call + * + * @param {String} url endpoint url + * @param {Object} data RPC parameters + * @param {Function} success RPC call success callback + * @param {Function} error RPC call error callback + * @returns {jQuery.Deferred} deferred object for the RPC call + */ rpc: function(url, data, success, error) { // TODO: support additional arguments ? - this.session.rpc(url, data, success, error); + return this.session.rpc(url, data, success, error); } }); diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 2fd4d9f8b6b..ad4028fb155 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -32,8 +32,6 @@ openerp.base.DataSet = openerp.base.Controller.extend( this._super(session); this.model = model; - this._fields = null; - this._ids = []; this._active_ids = null; this._active_id_index = 0; @@ -43,12 +41,6 @@ openerp.base.DataSet = openerp.base.Controller.extend( this._context = {}; }, start: function() { - // TODO: fields_view_get fields selection? - this.rpc("/base/dataset/fields", {"model":this.model}, this.on_fields); - }, - on_fields: function(result) { - this._fields = result.fields; - this.on_ready(); }, /** @@ -63,12 +55,11 @@ openerp.base.DataSet = openerp.base.Controller.extend( * @param {Number} [limit=null] The maximum number of records to return * @returns itself */ - fetch: function (offset, limit) { + fetch: function (fields, offset, limit) { offset = offset || 0; - limit = limit || null; this.rpc('/base/dataset/find', { model: this.model, - fields: this._fields, + fields: fields, domain: this._domain, context: this._context, sort: this._sort, @@ -77,9 +68,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( }, _.bind(function (records) { var data_records = _.map( records, function (record) { - return new openerp.base.DataRecord( - this.session, this.model, - this._fields, record); + return new openerp.base.DataRecord(this.session, this.model, fields, record); }, this); this.on_fetch(data_records, { @@ -116,16 +105,14 @@ openerp.base.DataSet = openerp.base.Controller.extend( * * @returns itself */ - active_ids: function () { + active_ids: function (fields) { this.rpc('/base/dataset/get', { ids: this.get_active_ids(), model: this.model }, _.bind(function (records) { this.on_active_ids(_.map( records, function (record) { - return new openerp.base.DataRecord( - this.session, this.model, - this._fields, record); + return new openerp.base.DataRecord(this.session, this.model, fields, record); }, this)); }, this)); return this; @@ -149,7 +136,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( * * @returns itself */ - active_id: function () { + active_id: function (fields) { this.rpc('/base/dataset/get', { ids: [this.get_active_id()], model: this.model @@ -158,7 +145,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( this.on_active_id( record && new openerp.base.DataRecord( this.session, this.model, - this._fields, record)); + fields, record)); }, this)); return this; }, diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 8310ca752e7..350ecddeee8 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -1,10 +1,24 @@ openerp.base.form = function (openerp) { -openerp.base.FormView = openerp.base.Controller.extend({ +openerp.base.views.add('form', 'openerp.base.FormView'); +openerp.base.FormView = openerp.base.Controller.extend( + /** @lends openerp.base.FormView# */{ + /** + * Indicates that this view is not searchable, and thus that no search + * view should be displayed (if there is one active). + */ + searchable: false, + /** + * @constructs + * @param {openerp.base.Session} session the current openerp session + * @param {String} element_id this view's root element id + * @param {openerp.base.DataSet} dataset the dataset this view will work with + * @param {String} view_id the identifier of the OpenERP view object + */ init: function(view_manager, session, element_id, dataset, view_id) { this._super(session, element_id); - this.view_manager; + this.view_manager = view_manager; this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -17,7 +31,7 @@ openerp.base.FormView = openerp.base.Controller.extend({ }, start: function() { //this.log('Starting FormView '+this.model+this.view_id) - this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded); + return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded); }, on_loaded: function(data) { var self = this; @@ -92,6 +106,8 @@ openerp.base.FormView = openerp.base.Controller.extend({ }, on_saved: function() { // Check response for exceptions, display error + }, + on_action: function (action) { } }); @@ -463,6 +479,10 @@ openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldDate"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.find('input').datepicker(); } }); diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 46402dc6e3e..86b9a4b4ea6 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -1,6 +1,7 @@ openerp.base.list = function (openerp) { +openerp.base.views.add('list', 'openerp.base.ListView'); openerp.base.ListView = openerp.base.Controller.extend({ init: function(view_manager, session, element_id, dataset, view_id) { this._super(session, element_id); @@ -9,6 +10,9 @@ openerp.base.ListView = openerp.base.Controller.extend({ this.model = dataset.model; this.view_id = view_id; this.name = ""; + // TODO: default to action.limit + // TODO: decide if limit is a property of DataSet and thus global to all views (calendar ?) + this.limit = 80; this.cols = []; @@ -20,7 +24,7 @@ openerp.base.ListView = openerp.base.Controller.extend({ }, start: function() { //this.log('Starting ListView '+this.model+this.view_id) - this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); }, on_loaded: function(data) { this.fields_view = data.fields_view; @@ -81,6 +85,20 @@ openerp.base.ListView = openerp.base.Controller.extend({ return record.values; })); + }, + do_search: function (domains, contexts, groupbys) { + var self = this; + this.rpc('/base/session/eval_domain_and_context', { + domains: domains, + contexts: contexts, + group_by_seq: groupbys + }, function (results) { + // TODO: handle non-empty results.group_by with read_group + self.dataset.set({ + context: results.context, + domain: results.domain + }).fetch(self.fields_view.fields, 0, self.limit); + }); } }); diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index 45f60372b25..fa016549b0e 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -15,7 +15,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({ }, start: function() { //this.log('Starting SearchView '+this.model+this.view_id) - this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + return this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + }, + show: function () { + this.$element.show(); + }, + hide: function () { + this.$element.hide(); }, /** * Builds a list of widget rows (each row is an array of widgets) @@ -442,122 +448,6 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({ } }); -openerp.base.search.custom_filters = new openerp.base.Registry({ - 'char': 'openerp.base.search.ExtendedSearchProposition.Char', - 'datetime': 'openerp.base.search.ExtendedSearchProposition.DateTime' -}); - -openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({ - template: 'SearchView.extended_search.proposition', - identifier_prefix: 'extended-search-proposition', - init: function (parent, fields) { - this._super(parent); - this.fields = _(fields).chain() - .map(function(val, key) { return _.extend({}, val, {'name': key}); }) - .sortBy(function(field) {return field.string;}) - .value(); - this.attrs = {_: _, fields: this.fields, selected: null}; - this.value = null; - }, - start: function () { - this._super(); - this.select_field(this.fields.length > 0 ? this.fields[0] : null); - var _this = this; - 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.stop(); - e.stopPropagation(); - e.preventDefault(); - }); - }, - changed: function() { - var nval = this.$element.find(".searchview_extended_prop_field").val(); - if(this.attrs.selected == null || nval != this.attrs.selected.name) { - this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); - } - }, - /** - * Selects the provided field object - * - * @param field a field descriptor object (as returned by fields_get, augmented by the field name) - */ - select_field: function(field) { - var _this = this; - if(this.attrs.selected != null) { - this.value.stop(); - this.value = null; - this.$element.find('.searchview_extended_prop_op').html(''); - } - this.attrs.selected = field; - if(field == null) { - return; - } - - try { - this.value = new (openerp.base.search.custom_filters.get_object(field.type)) - (this); - _.each(this.value.operators, function(operator) { - var option = jQuery('', {value: operator.value}) - .text(operator.text) - .appendTo(_this.$element.find('.searchview_extended_prop_op')); - }); - this.$element.find('.searchview_extended_prop_value').html( - this.value.render({})); - this.value.start(); - } catch (e) { - if (! e instanceof openerp.base.KeyNotFound) { - throw e; - } - this.attrs.selected = null; - this.log('Unknow field type ' + e.key); - } - }, - get_proposition: function() { - if ( this.attrs.selected == null) - return null; - var field = this.attrs.selected.name; - var op = this.$element.find('.searchview_extended_prop_op').val(); - var value = this.value.get_value(); - return [field, op, value]; - } -}); - -openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.extend({ - template: 'SearchView.extended_search.proposition.char', - identifier_prefix: 'extended-search-proposition-char', - operators: [ - {value: "ilike", text: "contains"}, - {value: "not ilike", text: "doesn't contain"}, - {value: "=", text: "is equal to"}, - {value: "!=", text: "is not equal to"}, - {value: ">", text: "greater than"}, - {value: "<", text: "less than"}, - {value: ">=", text: "greater or equal than"}, - {value: "<=", text: "less or equal than"} - ], - get_value: function() { - return this.$element.val(); - } -}); -openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.BaseWidget.extend({ - template: 'SearchView.extended_search.proposition.char', - identifier_prefix: 'extended-search-proposition-datetime', - operators: [ - {value: "=", text: "is equal to"}, - {value: "!=", text: "is not equal to"}, - {value: ">", text: "greater than"}, - {value: "<", text: "less than"}, - {value: ">=", text: "greater or equal than"}, - {value: "<=", text: "less or equal than"} - ], - get_value: function() { - return this.$element.val(); - } -}); - openerp.base.search.Input = openerp.base.search.Widget.extend( /** @lends openerp.base.search.Input# */{ /** @@ -779,15 +669,81 @@ openerp.base.search.SelectionField = openerp.base.search.Field.extend({ return this.$element.val(); } }); -openerp.base.search.DateTimeField = openerp.base.search.Field.extend({ - get_value: function () { - return this.$element.val(); +/** + * @class + * @extends openerp.base.search.Field + */ +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({ + dateFormat: 'yy-mm-dd' + }); + }, + stop: function () { + this.$element.find('input').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(); + + var from = values_array[0].value; + var 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}}); } }); -openerp.base.search.DateField = openerp.base.search.Field.extend({ - 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.IntegerField.extend({ // TODO: .relation, .context, .domain @@ -800,6 +756,122 @@ openerp.base.search.ManyToManyField = openerp.base.search.IntegerField.extend({ // TODO: .related_columns (Array), .context, .domain }); +openerp.base.search.custom_filters = new openerp.base.Registry({ + 'char': 'openerp.base.search.ExtendedSearchProposition.Char', + 'datetime': 'openerp.base.search.ExtendedSearchProposition.DateTime' +}); + +openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({ + template: 'SearchView.extended_search.proposition', + identifier_prefix: 'extended-search-proposition', + init: function (parent, fields) { + this._super(parent); + this.fields = _(fields).chain() + .map(function(val, key) { return _.extend({}, val, {'name': key}); }) + .sortBy(function(field) {return field.string;}) + .value(); + this.attrs = {_: _, fields: this.fields, selected: null}; + this.value = null; + }, + start: function () { + this._super(); + this.select_field(this.fields.length > 0 ? this.fields[0] : null); + var _this = this; + 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.stop(); + e.stopPropagation(); + e.preventDefault(); + }); + }, + changed: function() { + var nval = this.$element.find(".searchview_extended_prop_field").val(); + if(this.attrs.selected == null || nval != this.attrs.selected.name) { + this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); + } + }, + /** + * Selects the provided field object + * + * @param field a field descriptor object (as returned by fields_get, augmented by the field name) + */ + select_field: function(field) { + var _this = this; + if(this.attrs.selected != null) { + this.value.stop(); + this.value = null; + this.$element.find('.searchview_extended_prop_op').html(''); + } + this.attrs.selected = field; + if(field == null) { + return; + } + + try { + this.value = new (openerp.base.search.custom_filters.get_object(field.type)) + (this); + _.each(this.value.operators, function(operator) { + var option = jQuery('', {value: operator.value}) + .text(operator.text) + .appendTo(_this.$element.find('.searchview_extended_prop_op')); + }); + this.$element.find('.searchview_extended_prop_value').html( + this.value.render({})); + this.value.start(); + } catch (e) { + if (! e instanceof openerp.base.KeyNotFound) { + throw e; + } + this.attrs.selected = null; + this.log('Unknow field type ' + e.key); + } + }, + get_proposition: function() { + if ( this.attrs.selected == null) + return null; + var field = this.attrs.selected.name; + var op = this.$element.find('.searchview_extended_prop_op').val(); + var value = this.value.get_value(); + return [field, op, value]; + } +}); + +openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.extend({ + template: 'SearchView.extended_search.proposition.char', + identifier_prefix: 'extended-search-proposition-char', + operators: [ + {value: "ilike", text: "contains"}, + {value: "not ilike", text: "doesn't contain"}, + {value: "=", text: "is equal to"}, + {value: "!=", text: "is not equal to"}, + {value: ">", text: "greater than"}, + {value: "<", text: "less than"}, + {value: ">=", text: "greater or equal than"}, + {value: "<=", text: "less or equal than"} + ], + get_value: function() { + return this.$element.val(); + } +}); +openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.BaseWidget.extend({ + template: 'SearchView.extended_search.proposition.char', + identifier_prefix: 'extended-search-proposition-datetime', + operators: [ + {value: "=", text: "is equal to"}, + {value: "!=", text: "is not equal to"}, + {value: ">", text: "greater than"}, + {value: "<", text: "less than"}, + {value: ">=", text: "greater or equal than"}, + {value: "<=", text: "less or equal than"} + ], + get_value: function() { + return this.$element.val(); + } +}); + }; // 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 70615cb5b94..b8667b14ad2 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -8,7 +8,6 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ // process all kind of actions init: function(session, element_id) { this._super(session, element_id); - this.action = null; this.viewmanager = null; }, /** @@ -17,8 +16,10 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ */ do_action: function(action) { // instantiate the right controllers by understanding the action - this.action = action; if(action.type == "ir.actions.act_window") { + if (this.viewmanager) { + this.viewmanager.stop(); + } this.viewmanager = new openerp.base.ViewManager(this.session,this.element_id); this.viewmanager.do_action_window(action); this.viewmanager.start(); @@ -26,15 +27,18 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ } }); +/** + * Registry for all the main views + */ +openerp.base.views = new openerp.base.Registry(); openerp.base.ViewManager = openerp.base.Controller.extend({ -// This will be ViewManager Abstract/Common + // This will be ViewManager Abstract/Common init: function(session, element_id) { this._super(session, element_id); this.action = null; this.dataset = null; - this.searchview_id = false; this.searchview = null; - this.search_visible = true; + this.active_view = null; // this.views = { "list": { "view_id":1234, "controller": instance} } this.views = {}; this.sidebar = new openerp.base.Sidebar(null); @@ -48,10 +52,41 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ this.sidebar.stop(); this._super(); }, + /** + * Asks the view manager to switch visualization mode. + * + * @param {String} view_type type of view to display + * @returns {jQuery.Deferred} new view loading promise + */ on_mode_switch: function(view_type) { - for (var i in this.views) { - this.views[i].controller.$element.toggle(i === view_type); + var view_promise; + this.active_view = view_type; + var view = this.views[view_type]; + if (!view.controller) { + // Lazy loading of views + var controller = new (openerp.base.views.get_object(view_type))( + this, this.session, this.element_id + "_view_" + view_type, this.dataset, view.view_id); + view_promise = controller.start(); + this.views[view_type].controller = controller; } + + if (view.controller.searchable === false) { + this.searchview.hide(); + } else { + this.searchview.show(); + } + + this.$element + .find('.views-switchers button').removeAttr('disabled') + .filter('[data-view-type="' + view_type + '"]') + .attr('disabled', true); + + for (var i in this.views) { + if (this.views[i].controller) { + this.views[i].controller.$element.toggle(i === view_type); + } + } + return view_promise; }, /** * Extract search view defaults from the current action's context. @@ -70,51 +105,51 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ }); return defaults; }, + /** + * Sets up the current viewmanager's search view. + * + * @param action the action being executed + * @returns {jQuery.Deferred} search view startup deferred + */ + setup_search_view:function (action) { + var self = this; + if (this.searchview) { + this.searchview.stop(); + } + + var searchview = this.searchview = new openerp.base.SearchView( + this, this.session, this.element_id + "_search", + this.dataset, action.search_view_id[0] || false, + this.search_defaults()); + searchview.on_search.add(function() { + self.views[self.active_view].controller.do_search.apply(self, arguments); + }); + return searchview.start(); + }, do_action_window: function(action) { var self = this; - var prefix_id = "#" + this.element_id; this.action = action; this.dataset = new openerp.base.DataSet(this.session, action.res_model); this.dataset.start(); this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: action.views})); - this.searchview_id = false; - if(this.search_visible && action.search_view_id) { - this.searchview_id = action.search_view_id[0]; - var searchview = this.searchview = new openerp.base.SearchView( - this, this.session, this.element_id + "_search", - this.dataset, this.searchview_id, - this.search_defaults()); - searchview.on_search.add(this.do_search); - searchview.start(); + var searchview_loaded = this.setup_search_view(action); + + this.$element.find('.views-switchers button').click(function() { + self.on_mode_switch($(this).data('view-type')); + }); + _.each(action.views, function(view) { + self.views[view[1]] = { view_id: view[0], controller: null }; + }); - if (action['auto_search']) { - searchview.on_loaded.add_last( - searchview.do_search); - } - } - for(var i = 0; i < action.views.length; i++) { - var view_id, controller; - view_id = action.views[i][0]; - if(action.views[i][1] == "tree") { - controller = new openerp.base.ListView(this, this.session, this.element_id + "_view_tree", this.dataset, view_id); - controller.start(); - this.views.tree = { view_id: view_id, controller: controller }; - this.$element.find(prefix_id + "_button_tree").bind('click',function(){ - self.on_mode_switch("tree"); - }); - } else if(action.views[i][1] == "form") { - controller = new openerp.base.FormView(this, this.session, this.element_id + "_view_form", this.dataset, view_id); - controller.start(); - this.views.form = { view_id: view_id, controller: controller }; - this.$element.find(prefix_id + "_button_form").bind('click',function(){ - self.on_mode_switch("form"); - }); - } - } // switch to the first one in sequence - this.on_mode_switch("tree"); + var inital_view_loaded = this.on_mode_switch(action.views[0][1]); + + if (action['auto_search']) { + $.when(searchview_loaded, inital_view_loaded) + .then(this.searchview.do_search); + } }, // create when root, also add to parent when o2m on_create: function() { @@ -123,19 +158,10 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ }, on_edit: function() { }, - do_search: function (domains, contexts, groupbys) { - var self = this; - this.rpc('/base/session/eval_domain_and_context', { - domains: domains, - contexts: contexts, - group_by_seq: groupbys - }, function (results) { - // TODO: handle non-empty results.group_by with read_group - self.dataset.set({ - context: results.context, - domain: results.domain - }).fetch(0, self.action.limit); - }); + /** + * Called when one of the view want to execute an action + */ + on_action: function(action) { } }); @@ -262,19 +288,43 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({ } }); +openerp.base.views.add('calendar', 'openerp.base.CalendarView'); + openerp.base.CalendarView = openerp.base.Controller.extend({ -// Dhtmlx scheduler ? + start: function () { + this._super(); + this.$element.append('Calendar view'); + } }); +openerp.base.views.add('gantt', 'openerp.base.GanttView'); openerp.base.GanttView = openerp.base.Controller.extend({ -// Dhtmlx gantt ? + start: function () { + this._super(); + this.$element.append('Gantt view'); + } +}); + +openerp.base.views.add('tree', 'openerp.base.TreeView'); +/** + * Genuine tree view (the one displayed as a tree, not the list) + */ +openerp.base.TreeView = openerp.base.Controller.extend({ + start: function () { + this._super(); + this.$element.append('Tree view'); + } }); openerp.base.DiagramView = openerp.base.Controller.extend({ -// }); +openerp.base.views.add('graph', 'openerp.base.GraphView'); openerp.base.GraphView = openerp.base.Controller.extend({ + start: function () { + this._super(); + this.$element.append('Graph view'); + } }); openerp.base.ProcessView = openerp.base.Controller.extend({ diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 25bb399fd90..535d7291c59 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -128,19 +128,18 @@ - - - - - - - - - - - + + + + + + + + + + + + @@ -328,6 +327,21 @@ + + + + (?) + + + + to + + + + diff --git a/addons/base/static/test/dataset.js b/addons/base/static/test/dataset.js index 6492d505f31..8cbb23ad265 100644 --- a/addons/base/static/test/dataset.js +++ b/addons/base/static/test/dataset.js @@ -179,7 +179,9 @@ $(document).ready(function () { equal(event.limit, 42); start(); }); - dataset.fetch(20, 42); + dataset.fetch({ + name : { type : 'char' } + }, 20, 42); }); asyncTest("Domain and context propagation", 3, function () { var dataset = new openerp.base.DataSet( diff --git a/addons/base/static/test/search-date.js b/addons/base/static/test/search-date.js new file mode 100644 index 00000000000..ff06165d1b3 --- /dev/null +++ b/addons/base/static/test/search-date.js @@ -0,0 +1,73 @@ +$(document).ready(function () { + var openerp; + function get_widget(attrs) { + var widget = new openerp.base.search.DateField( + {attrs: attrs}, {name: 'foo'}, {inputs: []}); + $('#qunit-fixture').html(widget.render({})); + widget.start(); + return widget; + } + + module('search-date', { + setup: function () { + openerp = window.openerp.init(true); + window.openerp.base.chrome(openerp); + window.openerp.base.views(openerp); + window.openerp.base.search(openerp); + } + }); + test('no values', function () { + var widget = get_widget(); + deepEqual(widget.get_values(), {}); + strictEqual(widget.get_context(), null); + strictEqual(widget.get_domain(), null); + }); + test('filled from', function () { + var widget = get_widget(); + widget.$element.find('input:eq(0)').val('1912-06-23'); + + deepEqual(widget.get_values(), {from: '1912-06-23'}); + strictEqual(widget.get_context(), null); + deepEqual(widget.get_domain(), [['foo', '>=', '1912-06-23']]); + }); + test('filled to', function () { + var widget = get_widget(); + widget.$element.find('input:eq(1)').val('1954-06-07'); + + deepEqual(widget.get_values(), {to: '1954-06-07'}); + strictEqual(widget.get_context(), null); + deepEqual(widget.get_domain(), [['foo', '<=', '1954-06-07']]); + }); + test('filled both', function () { + var widget = get_widget(); + widget.$element.find('input:eq(0)').val('1912-06-23'); + widget.$element.find('input:eq(1)').val('1954-06-07'); + + deepEqual(widget.get_values(), {from: '1912-06-23', to: '1954-06-07'}); + strictEqual(widget.get_context(), null); + deepEqual(widget.get_domain(), + [['foo', '>=', '1912-06-23'], ['foo', '<=', '1954-06-07']]); + }); + test('custom context', function () { + var widget = get_widget({context: {__id: -1}}); + widget.$element.find('input:eq(0)').val('1912-06-23'); + widget.$element.find('input:eq(1)').val('1954-06-07'); + + deepEqual( + widget.get_context(), + {__id: -1, + own_values: { + self: {from: '1912-06-23', to: '1954-06-07'}}}); + }); + test('custom filter_domain', function () { + var widget = get_widget({filter_domain: {__id: -42}}); + widget.$element.find('input:eq(0)').val('1912-06-23'); + widget.$element.find('input:eq(1)').val('1954-06-07'); + + deepEqual( + widget.get_domain(), + {__id: -42, + own_values: { + self: {from: '1912-06-23', to: '1954-06-07'}}}); + }); +}); diff --git a/addons/base/static/test/test.html b/addons/base/static/test/test.html index 2baa33b7f4b..8f448e8f8c6 100644 --- a/addons/base/static/test/test.html +++ b/addons/base/static/test/test.html @@ -12,11 +12,18 @@ + + + - + + + OpenERP Base Test Suite @@ -24,7 +31,9 @@ + +