diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 085e48d89e0..3a6405e5311 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -357,6 +357,10 @@ class DataSet(openerpweb.Controller): reads = Model.read(ids, fields or False, request.context) reads.sort(key=lambda obj: ids.index(obj['id'])) return reads + + @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): @@ -424,6 +428,19 @@ class DataSet(openerpweb.Controller): m = req.session.model(model) r = m.default_get(fields, context) return {'result': r} + + @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 + def read(self, request, model, group_by_fields, domain=None, context=None): + Model = request.session.model(model) + return Model.read_group(domain or False, False, group_by_fields, 0, False, context or False) class View(openerpweb.Controller): def fields_view_get(self, request, model, view_id, view_type, diff --git a/addons/base/static/src/base.html b/addons/base/static/src/base.html index 93e95d32534..39909413397 100644 --- a/addons/base/static/src/base.html +++ b/addons/base/static/src/base.html @@ -24,10 +24,11 @@ + - + diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 7871d37d233..9b3ac6ef47c 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -221,8 +221,6 @@ body.openerp { height: 63px; width: 200px; margin-right: 10px; - line-height: 63px; - text-align: center; border: 1px solid white; border-right-color: black; border-bottom-color: black; @@ -232,7 +230,12 @@ body.openerp { filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#CECECE',GradientType=0 ); } .openerp .company_logo { - vertical-align: middle + margin-top: 7px; + margin-left: 10px; + display: block; + background: url(/base/static/src/img/logo.png); + width:180px; + height:46px; } .openerp .header_title { float: left; @@ -773,44 +776,61 @@ body.openerp { background-position: 21px 0; } -.openerp .kitten-mode-activated { +.openerp.kitten-mode-activated .main_table { background: url(http://placekitten.com/g/1500/800) repeat; } -.openerp .kitten-mode-activated .header { +.openerp.kitten-mode-activated .header { background: url(http://placekitten.com/g/211/65) repeat; } -.openerp .kitten-mode-activated .secondary_menu { +.openerp.kitten-mode-activated .secondary_menu { background: url(http://placekitten.com/g/212/100) repeat; } -.openerp .kitten-mode-activated .menu { +.openerp.kitten-mode-activated .menu { background: #828282; background: -moz-linear-gradient(top, #828282 0%, #4D4D4D 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#828282), color-stop(100%,#4D4D4D)); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#828282', endColorstr='#4D4D4D',GradientType=0 ); } -.openerp .kitten-mode-activated .menu a { +.openerp.kitten-mode-activated .menu a { background: none; } -.openerp .kitten-mode-activated .menu span { +.openerp.kitten-mode-activated .menu span { background: none; } -.openerp .kitten-mode-activated .sidebar-displaying-div li a, -.openerp .kitten-mode-activated .view-manager-main-content h2.oe_view_title, -.openerp .kitten-mode-activated .view-manager-main-content a.searchview_group_string, -.openerp .kitten-mode-activated .view-manager-main-content label { +.openerp.kitten-mode-activated .sidebar-displaying-div li a, +.openerp.kitten-mode-activated .oe-application .view-manager-main-content h2.oe_view_title, +.openerp.kitten-mode-activated .oe-application .view-manager-main-content a.searchview_group_string, +.openerp.kitten-mode-activated .oe-application .view-manager-main-content label { color: white; } -.openerp .kitten-mode-activated .menu, -.openerp .kitten-mode-activated .header_corner, -.openerp .kitten-mode-activated .secondary_menu div h3, -.openerp .kitten-mode-activated .sidebar-displaying-div h2, -.openerp .kitten-mode-activated .view-manager-main-content #oe_app_view_list, -.openerp .kitten-mode-activated .view-manager-main-content .oe_form_notebook, -.openerp .kitten-mode-activated .view-manager-main-content .oe_form_hpaned { +.openerp.kitten-mode-activated .menu, +.openerp.kitten-mode-activated .header_corner, +.openerp.kitten-mode-activated .header_title, +.openerp.kitten-mode-activated .secondary_menu div, +.openerp.kitten-mode-activated .oe-application, +.openerp.kitten-mode-activated .oe_footer, +.openerp.kitten-mode-activated .loading, +.openerp.kitten-mode-activated .ui-dialog { opacity:0.8; filter:alpha(opacity=80); } +.openerp.kitten-mode-activated .header .company_logo { + background: url(http://placekitten.com/g/180/46); +} +.openerp.kitten-mode-activated .loading { + background: #828282; + border-color: #828282; +} + +/* Many2one Autosearch */ +.openerp .ui_combo { + height: 20px; + margin-right: 1px; + top: 4px; + width: 22px; +} +/* ------------- End autocomplete ------- */ diff --git a/addons/base/static/src/img/icons/index1.jpeg b/addons/base/static/src/img/icons/index1.jpeg new file mode 100644 index 00000000000..f43c68d79ec Binary files /dev/null and b/addons/base/static/src/img/icons/index1.jpeg differ diff --git a/addons/base/static/src/img/icons/index2.jpeg b/addons/base/static/src/img/icons/index2.jpeg new file mode 100644 index 00000000000..ec9d1eb6d3c Binary files /dev/null and b/addons/base/static/src/img/icons/index2.jpeg differ diff --git a/addons/base/static/src/js/base.js b/addons/base/static/src/js/base.js index 86ef80877cb..19d608e0260 100644 --- a/addons/base/static/src/js/base.js +++ b/addons/base/static/src/js/base.js @@ -126,6 +126,7 @@ openerp.base = function(instance) { openerp.base.views(instance); openerp.base.search(instance); openerp.base.list(instance); + openerp.base.m2o(instance); openerp.base.form(instance); }; diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 4391fcf6f4e..2be3f28260f 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -790,10 +790,6 @@ openerp.base.Header = openerp.base.Controller.extend({ this.do_update(); }, do_update: function() { - if(jQuery.param != undefined && - jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { - this.kitten = 1; - } this.$element.html(QWeb.render("Header", this)); } }); @@ -897,7 +893,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({ var params = {}; if(jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { - params = {kitten:1}; + this.$element.addClass("kitten-mode-activated"); } this.$element.html(QWeb.render("Interface", params)); diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index da0f18001a8..95c09ef483f 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -3,15 +3,139 @@ openerp.base.data = function(openerp) { openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.base.DataGroup# */{ /** - * Management interface between views and the collection of selected OpenERP - * records (represents the view's state?) + * Management interface between views and grouped collections of OpenERP + * records. + * + * The root DataGroup is instantiated with the relevant information + * (a session, a model, a domain, a context and a group_by sequence), the + * domain and context may be empty. It is then interacted with via + * :js:func:`~openerp.base.DataGroup.list`, which is used to read the + * content of the current grouping level, and + * :js:func:`~openerp.base.DataGroup.get`, which is used to fetch an item + * of the current grouping level. * * @constructs * @extends openerp.base.Controller + * * @param {openerp.base.Session} session Current OpenERP session + * @param {String} model name of the model managed by this DataGroup + * @param {Array} domain search domain for this DataGroup + * @param {Object} context context of the DataGroup's searches + * @param {Array} group_by sequence of fields by which to group */ - init: function(session) { + init: function(session, model, domain, context, group_by) { this._super(session, null); + this.model = model; + this.context = context; + this.domain = domain; + + this.group_by = group_by; + + this.groups = null; + }, + fetch: function () { + // internal method + var d = new $.Deferred(); + var self = this; + + if (this.groups) { + d.resolveWith(this, [this.groups]); + } else { + this.rpc('/base/group/read', { + model: this.model, + context: this.context, + domain: this.domain, + group_by_fields: this.group_by + }, function () { }).then(function (response) { + self.groups = response.result; + // read_group results are annoying: they use the name of the + // field grouped on to hold the value and the count, so no + // generic access to those values is possible. + // Alias them to `value` and `length`. + d.resolveWith(self, [_(response.result).map(function (group) { + var field_name = self.group_by[0]; + return _.extend({}, group, { + // provide field used for grouping + grouped_on: field_name, + length: group[field_name + '_count'], + value: group[field_name] + }); + })]); + }, function () { + d.rejectWith.apply(d, self, [arguments]); + }); + } + return d.promise(); + }, + /** + * Retrieves the content of an item in the DataGroup, which results in + * either a DataSet or new DataGroup instance. + * + * Calling :js:func:`~openerp.base.DataGroup.get` without having called + * :js:func:`~openerp.base.DataGroup.list` beforehand will likely result + * in an error. + * + * The resulting :js:class:`~openerp.base.DataGroup` or + * :js:class:`~openerp.base.DataSet` will be provided through the relevant + * callback function. In both functions, the current DataGroup will be + * provided as context (``this``) + * + * @param {Number} index the index of the group to open in the datagroup's collection + * @param {Function} ifDataSet executed if the item results in a DataSet, provided with the new dataset as parameter + * @param {Function} ifDataGroup executed if the item results in a DataSet, provided with the new datagroup as parameter + */ + get: function (index, ifDataSet, ifDataGroup) { + var group = this.groups[index]; + if (!group) { + throw new Error("No group at index " + index); + } + + var child_context = _.extend({}, this.context, group.__context); + if (group.__context.group_by.length) { + var datagroup = new openerp.base.DataGroup( + this.session, this.model, group.__domain, child_context, + group.__context.group_by); + ifDataGroup.call(this, datagroup); + } else { + var dataset = new openerp.base.DataSetSearch(this.session, this.model); + dataset.domain = group.__domain; + dataset.context = child_context; + ifDataSet.call(this, dataset); + } + }, + /** + * Gathers the content of the current data group (the current grouping + * level), and provides it via a promise object to which callbacks should + * be added:: + * + * datagroup.list().then(function (list) { + * // manipulate list here + * }); + * + * The argument to the callback is the list of elements fetched, the + * context (``this``) is the datagroup itself. + * + * The items of a list are the standard objects returned by ``read_group`` + * with three properties added: + * + * ``length`` + * the number of records contained in the group (and all of its + * sub-groups). This does *not* provide the size of the "next level" + * of the group, unless the group is terminal (no more groups within + * it). + * ``grouped_on`` + * the name of the field this level was grouped on, this is mostly + * used for display purposes, in order to know the name of the current + * level of grouping. The ``grouped_on`` should be the same for all + * objects of the list. + * ``value`` + * the value which led to this group (this is the value all contained + * records have for the current ``grouped_on`` field name). + * + * @returns {$.Deferred} + */ + list: function () { + return this.fetch(); } }); @@ -89,12 +213,12 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. context: this.context }, callback); }, - create: function(data, callback) { + create: function(data, callback, error_callback) { return this.rpc('/base/dataset/create', { model: this.model, data: data, context: this.context - }, callback); + }, callback, error_callback); }, write: function (id, data, callback) { return this.rpc('/base/dataset/save', { @@ -119,6 +243,15 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. args: args }, callback); }, + name_search: function (search_str, callback) { + search_str = search_str || ''; + return this.rpc('/base/dataset/name_search', { + model: this.model, + search_str: search_str, + domain: this.domain || [], + context: this.context + }, callback); + }, exec_workflow: function (id, signal, callback) { return this.rpc('/base/dataset/exec_workflow', { model: this.model, diff --git a/addons/base/static/src/js/dates.js b/addons/base/static/src/js/dates.js index da4e948e282..8daea39773f 100644 --- a/addons/base/static/src/js/dates.js +++ b/addons/base/static/src/js/dates.js @@ -1,11 +1,21 @@ 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/; + 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"; @@ -17,6 +27,13 @@ openerp.base.parse_datetime = function(str) { 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; @@ -33,6 +50,13 @@ openerp.base.parse_date = function(str) { 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; @@ -49,7 +73,7 @@ openerp.base.parse_time = function(str) { return obj; }; -/** +/* * Just a simple function to add some '0' if an integer it too small. */ var fts = function(str, size) { @@ -61,8 +85,18 @@ var fts = function(str, size) { 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(! str) { + if (!obj) { return false; } return fts(obj.getUTCFullYear(),4) + "-" + fts(obj.getUTCMonth() + 1,2) + "-" @@ -70,20 +104,34 @@ openerp.base.format_datetime = function(obj) { + 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(! str) { + 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(! str) { + if (!obj) { return false; } return fts(obj.getHours(),2) + ":" + fts(obj.getMinutes(),2) + ":" + fts(obj.getSeconds(),2); }; -}; \ No newline at end of file +}; diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index fbe9e054365..dc631c1256a 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -2,7 +2,7 @@ openerp.base.form = function (openerp) { openerp.base.views.add('form', 'openerp.base.FormView'); -openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base.FormView# */{ +openerp.base.FormView = openerp.base.View.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). @@ -143,14 +143,21 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/); if (call) { var method = call[1], args = []; + var argument_replacement = { + 'False' : false, + 'True' : true, + 'None' : null + } _.each(call[2].split(','), function(a) { var field = _.trim(a); - if (self.fields[field]) { + if (field in argument_replacement) { + args.push(argument_replacement[field]); + } else if (self.fields[field]) { var value = self.fields[field].value; args.push(value == null ? false : value); } else { args.push(false); - this.log("warning : on_change can't find field " + field, onchange); + self.log("warning : on_change can't find field " + field, onchange); } }); var ajax = { @@ -365,7 +372,8 @@ openerp.base.form.compute_domain = function(expr, fields) { } } return _.indexOf(stack, false) == -1; -}, +} + openerp.base.form.Widget = openerp.base.Controller.extend({ init: function(view, node) { this.view = view; @@ -532,7 +540,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ on_confirmed: function() { var self = this; - this.execute_action( + this.view.execute_action( this.node.attrs, this.view.dataset, this.session.action_manager, this.view.datarecord.id, function (result) { self.log("Button returned", result); @@ -540,9 +548,6 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ }); } }); -// let WidgetButton execute actions -_.extend(openerp.base.form.WidgetButton.prototype, - openerp.base.ActionExecutor); openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({ init: function(view, node) { @@ -649,7 +654,24 @@ openerp.base.form.FieldChar = openerp.base.form.Field.extend({ openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({ init: function(view, node) { this._super(view, node); + this.template = "FieldEmail"; this.validation_regex = /@/; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.find('button').click(this.on_button_clicked); + }, + on_button_clicked: function() { + if (!this.value || this.invalid) { + this.notification.warn("E-mail error", "Can't send email to invalid e-mail address"); + } else { + location.href = 'mailto:' + this.value; + } + }, + set_value: function(value) { + this._super.apply(this, arguments); + var show_value = (value != null && value !== false) ? value : ''; + this.$element.find('a').attr('href', 'mailto:' + show_value); } }); @@ -681,32 +703,69 @@ openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({ } }); -openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({ +openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldDate"; - this.validation_regex = /^\d+-\d+-\d+$/; + this.jqueryui_object = 'datetimepicker'; }, start: function() { this._super.apply(this, arguments); - this.$element.find('input').change(this.on_ui_change).datepicker({ - dateFormat: 'yy-mm-dd' - }); - } -}); - -openerp.base.form.FieldDatetime = openerp.base.form.FieldChar.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldDatetime"; - this.validation_regex = /^\d+-\d+-\d+( \d+:\d+(:\d+)?)?$/; - }, - start: function() { - this._super.apply(this, arguments); - this.$element.find('input').change(this.on_ui_change).datetimepicker({ + this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({ dateFormat: 'yy-mm-dd', timeFormat: 'hh:mm:ss' }); + }, + set_value: function(value) { + this._super.apply(this, arguments); + if (value == null || value == false) { + this.$element.find('input').val(''); + } else { + this.value = this.parse(value); + this.$element.find('input')[this.jqueryui_object]('setDate', this.value); + } + }, + set_value_from_ui: function() { + this.value = this.$element.find('input')[this.jqueryui_object]('getDate') || false; + if (this.value) { + this.value = this.format(this.value); + } + }, + validate: function() { + this.invalid = this.required && this.value === false; + }, + parse: openerp.base.parse_datetime, + format: openerp.base.format_datetime +}); + +openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({ + init: function(view, node) { + this._super(view, node); + this.jqueryui_object = 'datepicker'; + }, + parse: openerp.base.parse_date, + format: openerp.base.format_date +}); + +openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({ + init: function(view, node) { + this._super(view, node); + this.validation_regex = /^\d+:\d+$/; + }, + set_value: function(value) { + value = value || 0; + this._super.apply(this, [value]); + var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60)); + this.$element.find('input').val(show_value); + }, + validate: function() { + if (typeof(this.value) == "string") { + this._super.apply(this, arguments); + if (!this.invalid) { + var time = this.value.split(':'); + this.value = parseInt(time[0], 10) + parseInt(time[1], 10) / 60; + } + } } }); @@ -827,10 +886,33 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({ } }); +openerp.base.form.FieldMany2OneDatasSet = openerp.base.DataSetStatic.extend({ + start: function() { + }, + write: function (id, data, callback) { + this._super(id, data, callback); + }, +}); + +openerp.base.form.FieldMany2OneViewManager = openerp.base.ViewManager.extend({ + init: function(session, element_id, dataset, views) { + this._super(session, element_id, dataset, views); + } +}); + openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldMany2One"; + this.is_field_m2o = true; + }, + start: function() { + this.$element = $('#' + this.element_id); + this.dataset = new openerp.base.form.FieldMany2OneDatasSet(this.session, this.field.relation); + var views = [ [false,"list"], [false,"form"] ]; + this.viewmanager = new openerp.base.form.FieldMany2OneViewManager(this.view.session, this.element_id, this.dataset, views); + new openerp.base.m2o(this.viewmanager, this.$element, this.field.relation, this.dataset, this.session) + this.$element.find('input').change(this.on_ui_change); }, set_value: function(value) { this._super.apply(this, arguments); @@ -840,6 +922,16 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ this.value = value[0]; } this.$element.find('input').val(show_value); + this.$element.find('input').attr('m2o_id', this.value); + }, + + get_value: function() { + var val = this.$element.find('input').attr('m2o_id') || this.value + return val; + }, + + on_ui_change: function() { + this.touched = this.view.touched = true; } }); @@ -900,6 +992,8 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ this._super(view, node); this.template = "FieldMany2Many"; this.list_id = _.uniqueId("many2many"); + this.is_started = false; + this.is_setted = false; }, start: function() { this._super.apply(this, arguments); @@ -909,23 +1003,35 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ var self = this; this.list_view.m2m_field = this; this.list_view.start(); + var hack = {loaded: false}; + this.list_view.on_loaded.add_last(function() { + if (! hack.loaded) { + self.is_started = true; + self.check_load(); + hack.loaded = true; + } + }); }, set_value: function(value) { if (value != false) { this.dataset.ids = value; this.dataset.count = value.length; - this.list_view.do_reload(); + this.is_setted = true; + this.check_load(); } }, get_value: function() { return [[6,false,this.dataset.ids]]; + }, + check_load: function() { + if(this.is_started && this.is_setted) { + this.list_view.do_reload(); + } } }); openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ - do_delete: function (e) { - e.stopImmediatePropagation(); - var ids = [this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value]; + do_delete: function (ids) { this.dataset.ids = _.without.apply(null, [this.dataset.ids].concat(ids)); this.dataset.count = this.dataset.ids.length; // there may be a faster way @@ -947,12 +1053,20 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ }, do_add_record: function (e) { e.stopImmediatePropagation(); - // TODO: need to open a popup with search view + var pop = new openerp.base.form.Many2XSelectPopup(null, this.m2m_field.view.session); + pop.select_element(this.model); + var self = this; + pop.on_select_element.add(function(element_id) { + if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) { + self.dataset.ids.push(element_id); + self.dataset.count = self.dataset.ids.length; + self.do_reload(); + } + pop.stop(); + }); }, - on_select_row: function(event) { - var $target = $(event.currentTarget); - var row = this.rows[$target.prevAll().length]; - var id = row.data.id.value; + select_record: function(index) { + var id = this.rows[index].data.id.value; if(! id) { return; } @@ -969,6 +1083,97 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ } }); +openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({ + identifier_prefix: "many2xselectpopup", + template: "Many2XSelectPopup", + select_element: function(model) { + this.model = model; + var html = this.render(); + jQuery(html).dialog({title: '', + modal: true, + minWidth: 800}); + this.start(); + }, + start: function() { + this._super(); + this.dataset = new openerp.base.DataSetSearch(this.session, this.model); + this.setup_search_view(); + }, + setup_search_view: function() { + var self = this; + if (this.searchview) { + this.searchview.stop(); + } + this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", + this.dataset, false, {}); + this.searchview.on_search.add(function(domains, contexts, groupbys) { + self.view_list.do_search.call( + self, domains, contexts, groupbys); + }); + this.searchview.on_loaded.add_last(function () { + var $buttons = self.searchview.$element.find(".oe_search-view-buttons"); + $buttons.append(QWeb.render("Many2XSelectPopup.search.buttons")); + var $nbutton = $buttons.find(".oe_many2xselectpopup-search-new"); + $nbutton.click(function() { + self.new_object(); + }); + var $cbutton = $buttons.find(".oe_many2xselectpopup-search-close"); + $cbutton.click(function() { + self.stop(); + }); + self.view_list = new openerp.base.form.Many2XPopupListView( null, self.session, + self.element_id + "_view_list", self.dataset, false); + self.view_list.popup = self; + self.view_list.do_show(); + self.view_list.start(); + var tmphack = {"loaded": false}; + self.view_list.on_loaded.add_last(function() { + if ( !tmphack.loaded ) { + self.view_list.do_reload(); + tmphack.loaded = true; + }; + }); + }); + this.searchview.start(); + }, + on_select_element: function(element_id) { + }, + new_object: function() { + var self = this; + this.searchview.hide(); + this.view_list.$element.hide(); + this.dataset.index = null; + this.view_form = new openerp.base.FormView({}, this.session, + this.element_id + "_view_form", this.dataset, false); + 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("Many2XSelectPopup.form.buttons")); + var $nbutton = $buttons.find(".oe_many2xselectpopup-form-save"); + $nbutton.click(function() { + self.view_form.do_save(); + }); + var $cbutton = $buttons.find(".oe_many2xselectpopup-form-close"); + $cbutton.click(function() { + self.stop(); + }); + }); + this.view_form.on_created.add_last(function(r, success) { + if (r.result) { + var id = arguments[0].result; + self.on_select_element(id); + } + }); + this.view_form.do_show(); + } +}); + +openerp.base.form.Many2XPopupListView = openerp.base.ListView.extend({ + switch_to_record: function(index) { + this.popup.on_select_element(this.dataset.ids[index]); + } +}); + openerp.base.form.FieldReference = openerp.base.form.Field.extend({ init: function(view, node) { this._super(view, node); @@ -976,6 +1181,17 @@ openerp.base.form.FieldReference = openerp.base.form.Field.extend({ } }); +openerp.base.form.FieldImage = openerp.base.form.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldImage"; + }, + set_value: function(value) { + this._super.apply(this, arguments); + this.$element.find('img').show().attr('src', 'data:image/png;base64,' + this.value); + } +}); + /** * Registry of form widgets, called by :js:`openerp.base.FormView` */ @@ -1002,7 +1218,8 @@ openerp.base.form.widgets = new openerp.base.Registry({ 'float' : 'openerp.base.form.FieldFloat', 'integer': 'openerp.base.form.FieldFloat', 'progressbar': 'openerp.base.form.FieldProgressBar', - 'float_time': 'openerp.base.form.FieldFloat' + 'float_time': 'openerp.base.form.FieldFloatTime', + 'image': 'openerp.base.form.FieldImage' }); }; diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 62d29da0cba..70ab1110baa 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -1,7 +1,6 @@ openerp.base.list = function (openerp) { openerp.base.views.add('list', 'openerp.base.ListView'); -openerp.base.ListView = openerp.base.Controller.extend( - /** @lends openerp.base.ListView# */ { +openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ { defaults: { // records can be selected one by one 'selectable': true, @@ -61,7 +60,11 @@ openerp.base.ListView = openerp.base.Controller.extend( columns: this.columns, rows: this.rows }); - $(this.list).bind({ + this.groups = new openerp.base.ListView.Groups({ + options: this.options, + columns: this.columns + }); + $([this.list, this.groups]).bind({ 'selected': function (e, selection) { self.$element.find('#oe-list-delete') .toggle(!!selection.length); @@ -80,7 +83,7 @@ openerp.base.ListView = openerp.base.Controller.extend( id, self.do_reload); }, 'row_link': function (e, index) { - self.switch_to_record(index); + self.select_record(index); } }); @@ -233,7 +236,7 @@ openerp.base.ListView = openerp.base.Controller.extend( * @param {Number|null} index the record index (in the current dataset) to switch to * @param {String} [view="form"] the view type to switch to */ - switch_to_record:function (index, view) { + select_record:function (index, view) { view = view || 'form'; this.dataset.index = index; _.delay(_.bind(function () { @@ -290,6 +293,14 @@ openerp.base.ListView = openerp.base.Controller.extend( // TODO: handle non-empty results.group_by with read_group self.dataset.context = results.context; self.dataset.domain = results.domain; + if (results.group_by.length) { + self.groups.datagroup = new openerp.base.DataGroup( + self.session, self.dataset.model, + results.domain, results.context, + results.group_by); + self.$element.html(self.groups.render()); + return; + } return self.do_reload(); }); }, @@ -338,7 +349,7 @@ openerp.base.ListView = openerp.base.Controller.extend( */ do_add_record: function () { this.notification.notify('Add', "New record"); - this.switch_to_record(null); + this.select_record(null); }, /** * Handles deletion of all selected lines @@ -349,10 +360,7 @@ openerp.base.ListView = openerp.base.Controller.extend( } // TODO: implement reorder (drag and drop rows) }); -_.extend(openerp.base.ListView.prototype, openerp.base.ActionExecutor); - -openerp.base.ListView.List = Class.extend( - /** @lends openerp.base.ListView.List# */{ +openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List# */{ /** * List display for the ListView, handles basic DOM events and transforms * them in the relevant higher-level events, to which the list view (or @@ -464,10 +472,54 @@ openerp.base.ListView.List = Class.extend( // drag and drop // editable? }); - -openerp.base.TreeView = openerp.base.Controller.extend({ +openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{ + /** + * Grouped display for the ListView. Handles basic DOM events and interacts + * with the :js:class:`~openerp.base.DataGroup` bound to it. + * + * Provides events similar to those of + * :js:class:`~openerp.base.ListView.List` + */ + init: function (opts) { + this.options = opts.options; + this.columns = opts.columns; + this.datagroup = {}; + }, + make_level: function (datagroup) { + var self = this, $root = $('
'); + datagroup.list().then(function (list) { + _(list).each(function (group, index) { + var $title = $('
') + .text(group.grouped_on + ': ' + group.value + ' (' + group.length + ')') + .appendTo($root); + $title.click(function () { + datagroup.get(index, function (new_dataset) { + var $content = $('