summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfwi-odoo <fwi@odoo.com>2017-04-25 13:55:38 +0200
committerfwi-odoo <fwi@odoo.com>2017-05-03 13:41:07 +0200
commita53335fdb2fe6e9232bde66f06e9499bece452d4 (patch)
tree662cb9a587ed2b076f0ffe4896d2e75ce120ae34
parent30384249e11d1d74b19bf2b63d4fd9fe6a99bad1 (diff)
[FIX] web, document: adapt toolbar to the new views
- Fix the toolbar behaviour since there is no more dataset in the widget. - Improve the communication beetwen the controller and the toolbar. - Move the code linked to the document upload in the right file. - Adapt the tests to mock the search_read done by ´document´ addon - Adapt the mock server to be able to give a custom toolbar action
-rw-r--r--addons/document/static/src/js/document.js178
-rw-r--r--addons/web/static/src/js/chrome/sidebar.js295
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js39
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js25
-rw-r--r--addons/web/static/src/js/views/form/form_view.js2
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js41
-rw-r--r--addons/web/static/src/js/views/list/list_view.js1
-rw-r--r--addons/web/static/src/xml/base.xml8
-rw-r--r--addons/web/static/tests/helpers/mock_server.js5
-rw-r--r--addons/web/static/tests/helpers/test_utils.js2
-rw-r--r--addons/web/static/tests/views/form_tests.js144
-rw-r--r--addons/web/static/tests/views/list_tests.js38
12 files changed, 552 insertions, 226 deletions
diff --git a/addons/document/static/src/js/document.js b/addons/document/static/src/js/document.js
index 05885839469..5fc69141583 100644
--- a/addons/document/static/src/js/document.js
+++ b/addons/document/static/src/js/document.js
@@ -1,33 +1,183 @@
-odoo.define('document.document', function(require) {
+odoo.define('document.document', function (require) {
"use strict";
var core = require('web.core');
+var Dialog = require('web.Dialog');
+var framework = require('web.framework');
var Sidebar = require('web.Sidebar');
var _t = core._t;
Sidebar.include({
- init : function(){
+ /**
+ * @override
+ */
+ init : function (parent, options) {
this._super.apply(this, arguments);
- var view = this.getParent();
- if (view.fields_view && view.fields_view.type === "form") {
+ if (options.viewType === "form") {
this.sections.splice(1, 0, { 'name' : 'files', 'label' : _t('Attachment(s)'), });
this.items.files = [];
}
+ this.fileuploadId = _.uniqueId('oe_fileupload');
+ $(window).on(this.fileuploadId, this._onFileUploaded.bind(this));
},
- on_attachments_loaded: function(attachments) {
+ /**
+ * Get the attachment linked to the record when the toolbar started
+ *
+ * @override
+ */
+ start: function () {
+ var _super = this._super.bind(this);
+ this._updateAttachments().then(_super);
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ $(window).off(this.fileuploadId);
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+ /**
+ * @override
+ */
+ updateEnv: function (env) {
+ this.env = env;
+ this._updateAttachments().then(this._redraw.bind(this));
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Process the attachments then rerender the toolbar
+ *
+ * @private
+ * @param {Object} attachments
+ */
+ _processAttachments: function (attachments) {
//to display number in name if more then one attachment which has same name.
- var self = this;
_.chain(attachments)
- .groupBy(function(attachment) { return attachment.name; })
- .each(function(attachment){
- if(attachment.length > 1)
- _.map(attachment, function(attachment, i){
- attachment.name = _.str.sprintf(_t("%s (%s)"), attachment.name, i+1);
- });
- });
- self._super(attachments);
+ .groupBy(function (attachment) { return attachment.name; })
+ .each(function (attachment) {
+ if (attachment.length > 1) {
+ _.map(attachment, function (attachment, i) {
+ attachment.name = _.str.sprintf(_t("%s (%s)"), attachment.name, i+1);
+ });
+ }
+ });
+ _.each(attachments,function (a) {
+ a.label = a.name;
+ if (a.type === "binary") {
+ a.url = '/web/content/' + a.id + '?download=true';
+ }
+ });
+ this.items.files = attachments;
+ },
+ /**
+ * @private
+ * @override
+ */
+ _redraw: function () {
+ this._super.apply(this, arguments);
+ this.$('.o_sidebar_add_attachment .o_form_binary_form')
+ .change(this._onAddAttachment.bind(this));
+ this.$('.o_sidebar_delete_attachment')
+ .click(this._onDeleteAttachment.bind(this));
},
+ /**
+ * Update the attachments to be displayed in the attachment section
+ * of the toolbar
+ *
+ * @private
+ */
+ _updateAttachments: function () {
+ var activeId = this.env.activeIds[0];
+ if (!activeId) {
+ this.items.files = [];
+ return $.when();
+ } else {
+ var domain = [
+ ['res_model', '=', this.env.model],
+ ['res_id', '=', activeId],
+ ['type', 'in', ['binary', 'url']]
+ ];
+ var fields = ['name', 'url', 'type',
+ 'create_uid', 'create_date', 'write_uid', 'write_date'];
+ return this._rpc({
+ model: 'ir.attachment',
+ method: 'search_read',
+ context: this.env.context,
+ domain: domain,
+ fields: fields,
+ }).then(this._processAttachments.bind(this));
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Method triggered when user click on 'add attachment' and select a file
+ *
+ * @private
+ * @param {Event} event
+ */
+ _onAddAttachment: function (event) {
+ var $event = $(event.target);
+ if ($event.val() !== '') {
+ var $binaryForm = this.$('form.o_form_binary_form');
+ $binaryForm.submit();
+ $binaryForm.find('input[type=file]').prop('disabled', true);
+ $binaryForm.find('button').prop('disabled', true).find('img, span').toggle();
+ this.$('.o_sidebar_add_attachment a').text(_t('Uploading...'));
+ framework.blockUI();
+ }
+ },
+ /**
+ * Method triggered when user delete an attachment
+ *
+ * @private
+ * @param {Event} event
+ */
+ _onDeleteAttachment: function (event) {
+ event.preventDefault();
+ var self = this;
+ var $event = $(event.currentTarget);
+ var options = {
+ confirm_callback: function () {
+ self._rpc({
+ model: 'ir.attachment',
+ method: 'unlink',
+ args: [parseInt($event.attr('data-id'), 10)],
+ })
+ .then(self._updateAttachments.bind(self))
+ .then(self._redraw.bind(self));
+ }
+ };
+ Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options);
+ },
+ /**
+ * Handler called when the upload is done
+ *
+ * @private
+ */
+ _onFileUploaded: function () {
+ var attachments = Array.prototype.slice.call(arguments, 1);
+ var uploadErrors = _.filter(attachments, function (attachment) {
+ return attachment.error;
+ });
+ if (uploadErrors.length) {
+ this.do_warn(_t('Uploading Error'), uploadErrors[0].error);
+ }
+ this._updateAttachments().then(this._redraw.bind(this));
+ framework.unblockUI();
+ }
});
});
diff --git a/addons/web/static/src/js/chrome/sidebar.js b/addons/web/static/src/js/chrome/sidebar.js
index 3cd17334e9e..bf9e3668c35 100644
--- a/addons/web/static/src/js/chrome/sidebar.js
+++ b/addons/web/static/src/js/chrome/sidebar.js
@@ -3,9 +3,6 @@ odoo.define('web.Sidebar', function (require) {
var Context = require('web.Context');
var core = require('web.core');
-var data = require('web.data');
-var Dialog = require('web.Dialog');
-var framework = require('web.framework');
var pyeval = require('web.pyeval');
var Widget = require('web.Widget');
@@ -13,12 +10,25 @@ var QWeb = core.qweb;
var _t = core._t;
var Sidebar = Widget.extend({
- init: function(parent, options) {
- var self = this;
- this._super(parent);
+ events: {
+ "click .dropdown-menu li a": "_onDropdownClicked"
+ },
+ /**
+ * @override
+ *
+ * @param {Object} options
+ * @param {Object} options.items
+ * @param {Object} options.sections
+ * @param {Object} options.env
+ * @param {Object} options.actions
+ *
+ */
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
'editable': true
});
+ this.env = options.env;
this.sections = options.sections || [
{name: 'print', label: _t('Print')},
{name: 'other', label: _t('Action')},
@@ -27,51 +37,37 @@ var Sidebar = Widget.extend({
print: [],
other: [],
};
- this.fileupload_id = _.uniqueId('oe_fileupload');
- $(window).on(this.fileupload_id, function() {
- var args = [].slice.call(arguments).slice(1);
- self.do_attachement_update(self.dataset, self.model_id,args);
- framework.unblockUI();
- });
+ if (options.actions) {
+ this._addToolbarActions(options.actions);
+ }
},
- start: function() {
- var self = this;
- this._super(this);
+ /**
+ * @override
+ */
+ start: function () {
+ this._super.apply(this, arguments);
this.$el.addClass('btn-group');
- this.redraw();
- this.$el.on('click','.dropdown-menu li a', function(event) {
- var section = $(this).data('section');
- var index = $(this).data('index');
- var item = self.items[section][index];
- if (item.callback) {
- item.callback.apply(self, [item]);
- } else if (item.action) {
- self.on_item_action_clicked(item);
- } else if (item.url) {
- return true;
- }
- event.preventDefault();
- });
+ this._redraw();
},
- destroy: function() {
- $(window).off(this.fileupload_id);
- return this._super.apply(this, arguments);
- },
- redraw: function() {
- this.$el.html(QWeb.render('Sidebar', {widget: this}));
- // Hides Sidebar sections when item list is empty
- this.$('.o_dropdown').each(function() {
- if (!$(this).find('li').length) {
- $(this).hide();
- }
- });
- this.$("[title]").tooltip({
- delay: { show: 500, hide: 0}
- });
- this.$('.o_sidebar_add_attachment .o_form_binary_form').change(this.on_attachment_changed);
- this.$('.o_sidebar_delete_attachment').click(this.on_attachment_delete);
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Update the env for the sidebar then rerender it.
+ *
+ * @param {Object} env
+ */
+ updateEnv: function (env) {
+ this.env = env;
+ this._redraw();
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
/**
* For each item added to the section:
*
@@ -86,7 +82,7 @@ var Sidebar = Widget.extend({
* function to call when the item is clicked in the sidebar, called
* with the item descriptor as its first argument (so information
* can be stored as additional keys on the object passed to
- * ``add_items``)
+ * ``_addItems``)
*
* ``classname`` (optional)
* ``@class`` set on the sidebar serialization of the item
@@ -94,135 +90,126 @@ var Sidebar = Widget.extend({
* ``title`` (optional)
* will be set as the item's ``@title`` (tooltip)
*
- * @param {String} section_code
+ * @private
+ * @param {String} sectionCode
* @param {Array<{label, action | callback[, classname][, title]}>} items
*/
- add_items: function(section_code, items) {
+ _addItems: function (sectionCode, items) {
if (items) {
- this.items[section_code].unshift.apply(this.items[section_code],items);
- this.redraw();
+ this.items[sectionCode].unshift.apply(this.items[sectionCode], items);
}
},
- add_toolbar: function(toolbar) {
+ /**
+ * Method that will add the custom actions to the toolbar
+ *
+ * @private
+ * @param {Object} toolbarActions
+ */
+ _addToolbarActions: function (toolbarActions) {
var self = this;
- _.each(['print','action','relate'], function(type) {
- var items = toolbar[type];
- if (items) {
- var actions = _.map(items, function (item) {
- return {
- label: item.name,
- action: item,
- };
- });
- self.add_items(type === 'print' ? 'print' : 'other', actions);
+ _.each(['print','action','relate'], function (type) {
+ if (type in toolbarActions) {
+ var actions = toolbarActions[type];
+ if (actions && actions.length) {
+ var items = _.map(actions, function (action) {
+ return {
+ label: action.name,
+ action: action,
+ };
+ });
+ self._addItems(type === 'print' ? 'print' : 'other', items);
+ }
}
});
+ if ('other' in toolbarActions) {
+ this._addItems('other', toolbarActions.other);
+ }
},
- on_item_action_clicked: function(item) {
+ /**
+ * Performs the action for the item clicked after getting the data
+ * necessary with a trigger up
+ *
+ * @private
+ * @param {Object} item
+ */
+ _onItemActionClicked: function (item) {
var self = this;
- self.getParent().sidebar_eval_context().done(function (sidebar_eval_context) {
- var ids = self.getParent().getSelectedIds();
- var domain;
- if (self.getParent().getActiveDomain) {
- domain = self.getParent().getActiveDomain();
- }
- else {
- domain = $.Deferred().resolve(undefined);
- }
- if (ids.length === 0) {
- new Dialog(this, {title: _t("Warning"), size: 'medium', $content: $("<div/>").html(_t("You must choose at least one record."))}).open();
- return false;
- }
- var dataset = self.getParent().dataset;
- var active_ids_context = {
- active_id: ids[0],
- active_ids: ids,
- active_model: dataset.model,
- };
-
- $.when(domain).done(function (domain) {
- if (domain !== undefined) {
- active_ids_context.active_domain = domain;
+ this.trigger_up('sidebar_data_asked', {
+ callback: function (env) {
+ self.env = env;
+ var activeIdsContext = {
+ active_id: env.activeIds[0],
+ active_ids: env.activeIds,
+ active_model: env.model,
+ };
+ if (env.domain) {
+ activeIdsContext.active_domain = env.domain;
}
- var c = pyeval.eval('context',
- new Context(
- sidebar_eval_context, active_ids_context));
+ var context = pyeval.eval('context', new Context(env.context, activeIdsContext));
self._rpc({
- route: '/web/action/load',
- params: {
- action_id: item.action.id,
- context: new Context(
- dataset.get_context(), active_ids_context).eval()
+ route: '/web/action/load',
+ params: {
+ action_id: item.action.id,
+ context: context,
+ },
+ }).done(function (result) {
+ result.context = new Context(
+ result.context || {}, activeIdsContext)
+ .set_eval_context(context);
+ result.flags = result.flags || {};
+ result.flags.new_window = true;
+ self.do_action(result, {
+ on_close: function () {
+ self.trigger_up('reload');
},
- })
- .done(function(result) {
- result.context = new Context(
- result.context || {}, active_ids_context)
- .set_eval_context(c);
- result.flags = result.flags || {};
- result.flags.new_window = true;
- self.do_action(result, {
- on_close: function() {
- // reload view
- self.getParent().reload();
- },
- });
});
- });
+ });
+ }
});
},
- do_attachement_update: function(dataset, model_id, args) {
- this.dataset = dataset;
- this.model_id = model_id;
- var upload_error = _.filter(args, function(attachment) {return attachment.error;});
- if (upload_error.length) {
- this.do_warn(_t('Uploading Error'), upload_error[0].error);
- }
- if (!model_id) {
- this.on_attachments_loaded([]);
- } else {
- var dom = [ ['res_model', '=', dataset.model], ['res_id', '=', model_id], ['type', 'in', ['binary', 'url']] ];
- var ds = new data.DataSetSearch(this, 'ir.attachment', dataset.get_context(), dom);
- ds.read_slice(['name', 'url', 'type', 'create_uid', 'create_date', 'write_uid', 'write_date'], {}).done(this.on_attachments_loaded);
- }
- },
- on_attachments_loaded: function(attachments) {
- _.each(attachments,function(a) {
- a.label = a.name;
- if(a.type === "binary") {
- a.url = '/web/content/' + a.id + '?download=true';
+ /**
+ * Method that renders the sidebar when there is a data update
+ *
+ * @private
+ */
+ _redraw: function () {
+ this.$el.html(QWeb.render('Sidebar', {widget: this}));
+
+ // Hides Sidebar sections when item list is empty
+ this.$('.o_dropdown').each(function () {
+ if (!$(this).find('li').length) {
+ $(this).hide();
}
});
- this.items.files = attachments;
- this.redraw();
+ this.$("[title]").tooltip({
+ delay: { show: 500, hide: 0}
+ });
},
- on_attachment_changed: function(e) {
- var $e = $(e.target);
- if ($e.val() !== '') {
- this.$('form.o_form_binary_form').submit();
- $e.parent().find('input[type=file]').prop('disabled', true);
- $e.parent().find('button').prop('disabled', true).find('img, span').toggle();
- this.$('.o_sidebar_add_attachment a').text(_t('Uploading...'));
- framework.blockUI();
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Method triggered when the user clicks on a toolbar dropdown
+ *
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onDropdownClicked: function (event) {
+ var section = $(event.currentTarget).data('section');
+ var index = $(event.currentTarget).data('index');
+ var item = this.items[section][index];
+ if (item.callback) {
+ item.callback.apply(this, [item]);
+ } else if (item.action) {
+ this._onItemActionClicked(item);
+ } else if (item.url) {
+ return true;
}
+ event.preventDefault();
},
- on_attachment_delete: function(e) {
- e.preventDefault();
- e.stopPropagation();
- var self = this;
- var $e = $(e.currentTarget);
- var options = {
- confirm_callback: function () {
- new data.DataSet(this, 'ir.attachment')
- .unlink([parseInt($e.attr('data-id'), 10)])
- .done(function() {
- self.do_attachement_update(self.dataset, self.model_id);
- });
- }
- };
- Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options);
- }
});
return Sidebar;
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index 33f6727987e..87e8d1a6664 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -19,6 +19,7 @@ var _t = core._t;
var BasicController = AbstractController.extend(FieldManagerMixin, {
custom_events: _.extend({}, AbstractController.prototype.custom_events, FieldManagerMixin.custom_events, {
reload: '_onReload',
+ sidebar_data_asked: '_onSidebarDataAsked'
}),
/**
* @override
@@ -127,6 +128,14 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
});
},
/**
+ * Method that will be overriden by the views with the ability to have selected ids
+ *
+ * @returns []
+ */
+ getSelectedIds: function () {
+ return [];
+ },
+ /**
* @override
*/
renderPager: function ($node, options) {
@@ -288,6 +297,19 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
}
},
/**
+ * Returns the new sidebar env
+ *
+ * @private
+ * @return {Object} the new sidebar env
+ */
+ _getSidebarEnv: function () {
+ return {
+ context: this.model.get(this.handle).getContext(),
+ activeIds: this.getSelectedIds(),
+ model: this.modelName,
+ };
+ },
+ /**
* Used by list and kanban views to determine whether or not to display
* the no content helper (if there is no data in the state to display)
*
@@ -389,9 +411,15 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
/**
* Helper method, to get the current environment variables from the model
* and notifies the component chain (by bubbling an event up)
+ *
+ * @private
*/
_updateEnv: function () {
var env = this.model.get(this.handle, {env: true});
+ if (this.sidebar) {
+ var sidebarEnv = this._getSidebarEnv();
+ this.sidebar.updateEnv(sidebarEnv);
+ }
this.trigger_up('env_updated', env);
},
/**
@@ -457,6 +485,17 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
this.reload({fieldNames: data.fieldNames});
}
},
+ /**
+ * Handler used to get all the data necessary when a custom action is
+ * performed through the sidebar.
+ *
+ * @private
+ * @param {OdooEvent} event
+ */
+ _onSidebarDataAsked: function (event) {
+ var sidebarEnv = this._getSidebarEnv();
+ event.data.callback(sidebarEnv);
+ },
});
return BasicController;
diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index e3b129c7915..f15bdfa5382 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -21,6 +21,9 @@ var FormController = BasicController.extend({
}),
/**
* @override
+ *
+ * @param {boolean} params.hasSidebar
+ * @param {Object} params.toolbarActions
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
@@ -29,7 +32,7 @@ var FormController = BasicController.extend({
this.footerToButtons = params.footerToButtons;
this.defaultButtons = params.defaultButtons;
this.hasSidebar = params.hasSidebar;
- this.toolbar = params.toolbar;
+ this.toolbarActions = params.toolbarActions || {};
},
//--------------------------------------------------------------------------
@@ -71,13 +74,12 @@ var FormController = BasicController.extend({
* Returns the current res_id, wrapped in a list. This is only used by the
* sidebar (and the debugmanager)
*
- * @todo This should be private. Need to change sidebar code
+ * @override
*
* @returns {number[]} either [current res_id] or []
*/
getSelectedIds: function () {
var env = this.model.get(this.handle, {env: true});
- // FIX ME : fix sidebar widget
return env.currentId ? [env.currentId] : [];
},
/**
@@ -137,12 +139,6 @@ var FormController = BasicController.extend({
**/
renderSidebar: function ($node) {
if (!this.sidebar && this.hasSidebar) {
- this.sidebar = new Sidebar(this, {
- editable: this.is_action_enabled('edit')
- });
- if (this.toolbar) {
- this.sidebar.add_toolbar(this.toolbar);
- }
var otherItems = [];
if (this.is_action_enabled('delete')) {
otherItems.push({
@@ -156,7 +152,16 @@ var FormController = BasicController.extend({
callback: this._onDuplicateRecord.bind(this),
});
}
- this.sidebar.add_items('other', otherItems);
+ this.sidebar = new Sidebar(this, {
+ editable: this.is_action_enabled('edit'),
+ viewType: 'form',
+ env: {
+ context: this.model.get(this.handle).getContext(),
+ activeIds: this.getSelectedIds(),
+ model: this.modelName,
+ },
+ actions: _.extend(this.toolbarActions, {other: otherItems}),
+ });
this.sidebar.appendTo($node);
// Show or hide the sidebar according to the view mode
diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js
index 4fff1a74ee4..aa6bdf69755 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -30,7 +30,7 @@ var FormView = BasicView.extend({
this.loadParams.parentID = params.parentID;
this.controllerParams.hasSidebar = params.sidebar;
- this.controllerParams.toolbar = false;
+ this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.footerToButtons = params.footer_to_buttons;
if ('action' in params && 'flags' in params.action) {
this.controllerParams.footerToButtons = params.action.flags.footer_to_buttons;
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index 50f47a73fc0..f2b15cca349 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -30,16 +30,15 @@ var ListController = BasicController.extend({
* @constructor
* @override
* @param {Object} params
- * @param {Object} params
* @param {boolean} params.editable
* @param {boolean} params.hasSidebar
- * @param {boolean} params.hasToolbar
+ * @param {Object} params.toolbarActions
* @param {boolean} params.noLeaf
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.hasSidebar = params.hasSidebar;
- this.hasToolbar = params.hasToolbar;
+ this.toolbarActions = params.toolbarActions || {};
this.editable = params.editable;
this.noLeaf = params.noLeaf;
this.selectedRecords = []; // there is no selected record by default
@@ -72,7 +71,7 @@ var ListController = BasicController.extend({
* if the header checkbox has been checked. This is done by evaluating the
* search results, and then adding the dataset domain (i.e. action domain).
*
- * @todo This is done only for the sidebar. The full mechanism is wrong,
+ * @todo This is done only for the data export. The full mechanism is wrong,
* this method should be private, most of the code in the sidebar should be
* moved to the controller, and we should not use the getParent method...
*
@@ -99,7 +98,7 @@ var ListController = BasicController.extend({
* Returns the list of currently selected res_ids (with the check boxes on
* the left)
*
- * @todo This should be private. Need to change sidebar code
+ * @override
*
* @returns {number[]} list of res_ids
*/
@@ -135,12 +134,6 @@ var ListController = BasicController.extend({
*/
renderSidebar: function ($node) {
if (this.hasSidebar && !this.sidebar) {
- this.sidebar = new Sidebar(this, {
- editable: this.is_action_enabled('edit')
- });
- if (this.hasToolbar) {
- this.sidebar.add_toolbar(this.fields_view.toolbar);
- }
var other = [{
label: _t("Export"),
callback: this._onExportData.bind(this)
@@ -160,8 +153,16 @@ var ListController = BasicController.extend({
label: _t('Delete'),
callback: this._onDeleteSelectedRecords.bind(this)
});
+ this.sidebar = new Sidebar(this, {
+ editable: this.is_action_enabled('edit'),
+ env: {
+ context: this.model.get(this.handle).getContext(),
+ activeIds: this.getSelectedIds(),
+ model: this.modelName,
+ },
+ actions: _.extend(this.toolbarActions, {other: other}),
+ });
}
- this.sidebar.add_items('other', other);
this.sidebar.appendTo($node);
this._toggleSidebar();
@@ -233,6 +234,15 @@ var ListController = BasicController.extend({
return this.renderer.confirmSave(state, id);
},
/**
+ * @override
+ * @private
+ */
+ _getSidebarEnv: function() {
+ var env = this._super.apply(this, arguments);
+ var record = this.model.get(this.handle);
+ return _.extend(env, {domain: record.getDomain()});
+ },
+ /**
* Allows to change the mode of a single row.
*
* @override
@@ -381,6 +391,13 @@ var ListController = BasicController.extend({
this._toggleSidebar();
},
/**
+ * @override
+ */
+ _onSidebarDataAsked: function (event) {
+ var env = this._getSidebarEnv();
+ event.data.callback(env);
+ },
+ /**
* Called when clicking on 'Archive' or 'Unarchive' in the sidebar.
*
* @private
diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js
index 7b597c712bc..61612282bf5 100644
--- a/addons/web/static/src/js/views/list/list_view.js
+++ b/addons/web/static/src/js/views/list/list_view.js
@@ -41,6 +41,7 @@ var ListView = BasicView.extend({
this.controllerParams.editable = arch.attrs.editable;
this.controllerParams.hasSidebar = params.sidebar;
+ this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.activeActions.delete = true;
this.controllerParams.noLeaf = !!this.loadParams.context.group_by_no_leaf;
this.controllerParams.mode = mode;
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index fec8dd8c7ee..6ffe8fca2db 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -241,12 +241,12 @@
</li>
<li t-if="section.name == 'files' and widget.options.editable" class="o_sidebar_add_attachment">
<t t-call="HiddenInputFile">
- <t t-set="fileupload_id" t-value="widget.fileupload_id"/>
+ <t t-set="fileupload_id" t-value="widget.fileuploadId"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<t t-set="multi_upload" t-value="true"/>
- <input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
- <input type="hidden" name="id" t-att-value="widget.model_id"/>
- <input type="hidden" name="session_id" t-att-value="widget.session.session_id" t-if="widget.session.override_session"/>
+ <input type="hidden" name="model" t-att-value="widget.env and widget.env.model"/>
+ <input type="hidden" name="id" t-att-value="widget.env.activeIds[0]"/>
+ <input type="hidden" name="session_id" t-att-value="widget.getSession().session_id" t-if="widget.getSession().override_session"/>
<span>Add...</span>
</t>
</li>
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index cbd1f1b1e1f..60024292edd 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -61,12 +61,15 @@ var MockServer = Class.extend({
* @param {string} model a model name (that should be in this.data)
* @returns {Object} an object with 2 keys: arch and fields
*/
- fieldsViewGet: function (arch, model) {
+ fieldsViewGet: function (arch, model, toolbar) {
var fields = $.extend(true, {}, this.data[model].fields);
var fvg = this._fieldsViewGet(arch, model, fields);
var fields_views = {};
fields_views[fvg.type] = fvg;
data_manager.processViews(fields_views, fields);
+ if (toolbar) {
+ fvg.toolbar = toolbar;
+ }
return fields_views[fvg.type];
},
/**
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 1c04548a218..516971f96fe 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -126,7 +126,7 @@ function createAsyncView(params) {
// add mock environment: mock server, session, fieldviewget, ...
var mockServer = addMockEnvironment(widget, params);
- var viewInfo = mockServer.fieldsViewGet(params.arch, params.model);
+ var viewInfo = mockServer.fieldsViewGet(params.arch, params.model, params.toolbar);
// create the view
var viewOptions = {
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 4b6f43add2b..9295f0fa236 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -1082,6 +1082,12 @@ QUnit.module('Views', {
'</form>',
viewOptions: {sidebar: true},
res_id: 1,
+ mockRPC: function(route, args) {
+ if (args.method === 'search_read' && args.model === 'ir.attachment') {
+ return $.when([]);
+ }
+ return this._super.apply(this, arguments);
+ },
});
assert.ok(!form.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible');
@@ -1221,6 +1227,12 @@ QUnit.module('Views', {
'</form>',
res_id: 1,
viewOptions: {sidebar: true},
+ mockRPC: function(route, args) {
+ if (args.method === 'search_read' && args.model === 'ir.attachment') {
+ return $.when([]);
+ }
+ return this._super.apply(this, arguments);
+ },
});
assert.strictEqual(form.get('title'), 'first record',
@@ -1668,6 +1680,12 @@ QUnit.module('Views', {
arch: '<form string="Partners"><field name="foo"></field></form>',
res_id: 1,
viewOptions: {sidebar: true},
+ mockRPC: function(route, args) {
+ if (args.method === 'search_read' && args.model === 'ir.attachment') {
+ return $.when([]);
+ }
+ return this._super.apply(this, arguments);
+ },
});
form.$buttons.find('.o_form_button_edit').click();
@@ -2009,6 +2027,12 @@ QUnit.module('Views', {
sidebar: true,
},
res_id: 1,
+ mockRPC: function(route, args) {
+ if (args.method === 'search_read' && args.model === 'ir.attachment') {
+ return $.when([]);
+ }
+ return this._super.apply(this, arguments);
+ },
});
assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1');
@@ -2048,6 +2072,9 @@ QUnit.module('Views', {
},
res_id: 1,
mockRPC: function (route, args) {
+ if (args.method === 'search_read' && args.model === 'ir.attachment') {
+ return $.when([]);
+ }
assert.step(args.method);
return this._super.apply(this, arguments);
}
@@ -3598,47 +3625,106 @@ QUnit.module('Views', {
});
QUnit.test('onchanges are applied before checking if it can be saved', function (assert) {
- assert.expect(4);
-
- this.data.partner.onchanges.foo = function (obj) {};
- this.data.partner.fields.foo.required = true;
-
- var def = $.Deferred();
+ assert.expect(4);
+
+ this.data.partner.onchanges.foo = function (obj) {};
+ this.data.partner.fields.foo.required = true;
+
+ var def = $.Deferred();
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet><group>' +
+ '<field name="foo"/>' +
+ '</group></sheet>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ var result = this._super.apply(this, arguments);
+ assert.step(args.method);
+ if (args.method === 'onchange') {
+ return def.then(function () {
+ return result;
+ });
+ }
+ return result;
+ },
+ intercepts: {
+ warning: function () {
+ assert.step('warning');
+ },
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('input[name="foo"]').val('').trigger("input");
+ form.$buttons.find('.o_form_button_save').click();
+
+ def.resolve();
+
+ assert.verifySteps(['read', 'onchange', 'warning'])
+ form.destroy();
+ });
+
+ QUnit.test('display toolbar', function (assert) {
+ assert.expect(7);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
+ res_id: 1,
arch: '<form string="Partners">' +
- '<sheet><group>' +
- '<field name="foo"/>' +
- '</group></sheet>' +
+ '<group><field name="bar"/></group>' +
'</form>',
- res_id: 2,
- mockRPC: function (route, args) {
- var result = this._super.apply(this, arguments);
- assert.step(args.method);
- if (args.method === 'onchange') {
- return def.then(function () {
- return result;
- });
- }
- return result;
+ toolbar: {
+ action: [{
+ model_name: 'partner',
+ name: 'Action partner',
+ type: 'ir.actions.server',
+ usage: 'ir_actions_server',
+ }],
+ print: [],
},
- intercepts: {
- warning: function () {
- assert.step('warning');
- },
+ viewOptions: {
+ sidebar: true,
+ },
+ mockRPC: function(route, args) {
+ if (route === '/web/action/load') {
+ assert.strictEqual(args.context.active_id, 1,
+ "the active_id shoud be 1.");
+ assert.deepEqual(args.context.active_ids, [1],
+ "the active_ids should be an array with 1 inside.");
+ return $.when({});
+ }
+ if (args.method === 'search_read' && args.model === 'ir.attachment') {
+ return $.when([]);
+ }
+ return this._super.apply(this, arguments);
},
});
- form.$buttons.find('.o_form_button_edit').click();
- form.$('input[name="foo"]').val('').trigger("input");
- form.$buttons.find('.o_form_button_save').click();
-
- def.resolve();
+ var $dropdowns = $('.o_web_client .o_control_panel .btn-group .o_dropdown_toggler_btn');
+ assert.strictEqual($dropdowns.length, 3,
+ "there should be 3 dropdowns (print, attachment, action) in the toolbar.");
+ var $actions = $('.o_web_client .o_control_panel .btn-group .dropdown-menu')[2].children;
+ assert.strictEqual($actions.length, 3,
+ "there should be 3 actions");
+ var $customAction = $('.o_web_client .o_control_panel .btn-group .dropdown-menu li a')[2];
+ assert.strictEqual($customAction.text.trim(), 'Action partner',
+ "the custom action should have 'Action partner' as name");
+ testUtils.intercept(form, 'do_action', function (event) {
+ var context = event.data.action.context.__contexts[1];
+ assert.strictEqual(context.active_id, 1,
+ "the active_id shoud be 1.");
+ assert.deepEqual(context.active_ids, [1],
+ "the active_ids should be an array with 1 inside.");
+ });
+ $customAction.click();
- assert.verifySteps(['read', 'onchange', 'warning'])
form.destroy();
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 4a8c87be598..0b754c2b5bb 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -574,6 +574,9 @@ QUnit.module('Views', {
viewOptions: {sidebar: true},
arch: '<tree><field name="foo"/></tree>',
mockRPC: function (route) {
+ if (route === '/web/dataset/call_kw/ir.attachment/search_read') {
+ return $.when([]);
+ }
assert.step(route);
return this._super.apply(this, arguments);
},
@@ -1693,6 +1696,41 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('display toolbar', function (assert) {
+ assert.expect(3);
+
+ var list = createView({
+ View: ListView,
+ model: 'event',
+ data: this.data,
+ arch: '<tree><field name="name"/></tree>',
+ toolbar: {
+ action: [{
+ model_name: 'event',
+ name: 'Action event',
+ type: 'ir.actions.server',
+ usage: 'ir_actions_server',
+ }],
+ print: [],
+ },
+ viewOptions: {
+ sidebar: true,
+ },
+ });
+
+ var $dropdowns = $('.o_web_client .o_control_panel .btn-group .o_dropdown_toggler_btn');
+ assert.strictEqual($dropdowns.length, 2,
+ "there should be 2 dropdowns in the toolbar.");
+ var $actions = $('.o_web_client .o_control_panel .btn-group .dropdown-menu')[1].children;
+ assert.strictEqual($actions.length, 3,
+ "there should be 3 actions");
+ var $customAction = $('.o_web_client .o_control_panel .btn-group .dropdown-menu li a')[2];
+ assert.strictEqual($customAction.text.trim(), 'Action event',
+ "the custom action should have 'Action event' as name");
+
+ list.destroy();
+ });
+
QUnit.test('edit list line after line deletion', function (assert) {
assert.expect(5);