[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
This commit is contained in:
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
on_item_action_clicked: 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;
|
||||
_.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);
|
||||
}
|
||||
var c = pyeval.eval('context',
|
||||
new Context(
|
||||
sidebar_eval_context, active_ids_context));
|
||||
|
||||
self._rpc({
|
||||
route: '/web/action/load',
|
||||
params: {
|
||||
action_id: item.action.id,
|
||||
context: new Context(
|
||||
dataset.get_context(), active_ids_context).eval()
|
||||
},
|
||||
})
|
||||
.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';
|
||||
}
|
||||
});
|
||||
this.items.files = attachments;
|
||||
this.redraw();
|
||||
},
|
||||
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();
|
||||
if ('other' in toolbarActions) {
|
||||
this._addItems('other', toolbarActions.other);
|
||||
}
|
||||
},
|
||||
on_attachment_delete: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
/**
|
||||
* 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;
|
||||
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);
|
||||
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 context = pyeval.eval('context', new Context(env.context, activeIdsContext));
|
||||
self._rpc({
|
||||
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');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 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.$("[title]").tooltip({
|
||||
delay: { show: 500, hide: 0}
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 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();
|
||||
},
|
||||
});
|
||||
|
||||
return Sidebar;
|
||||
|
||||
@@ -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
|
||||
@@ -126,6 +127,14 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
self._setMode('readonly', recordID);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Method that will be overriden by the views with the ability to have selected ids
|
||||
*
|
||||
* @returns []
|
||||
*/
|
||||
getSelectedIds: function () {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -287,6 +296,19 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
doIt();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
@@ -232,6 +233,15 @@ var ListController = BasicController.extend({
|
||||
var state = this.model.get(this.handle);
|
||||
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.
|
||||
*
|
||||
@@ -380,6 +390,13 @@ var ListController = BasicController.extend({
|
||||
this.selectedRecords = event.data.selection;
|
||||
this._toggleSidebar();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onSidebarDataAsked: function (event) {
|
||||
var env = this._getSidebarEnv();
|
||||
event.data.callback(env);
|
||||
},
|
||||
/**
|
||||
* Called when clicking on 'Archive' or 'Unarchive' in the sidebar.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
assert.expect(4);
|
||||
|
||||
this.data.partner.onchanges.foo = function (obj) {};
|
||||
this.data.partner.fields.foo.required = true;
|
||||
this.data.partner.onchanges.foo = function (obj) {};
|
||||
this.data.partner.fields.foo.required = true;
|
||||
|
||||
var def = $.Deferred();
|
||||
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();
|
||||
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();
|
||||
|
||||
def.resolve();
|
||||
|
||||
assert.verifySteps(['read', 'onchange', 'warning'])
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user