diff --git a/CHANGELOG b/CHANGELOG
index 9c0f1b33d2..5968613414 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
+* Remove dialog for input file
+* Use dropdown for attachment
* Manage icons in input
* Support RTL language direction
* Apply factor on domain parser
diff --git a/src/common.js b/src/common.js
index f1954e4568..b9e7f5a697 100644
--- a/src/common.js
+++ b/src/common.js
@@ -3412,6 +3412,19 @@
};
+ Sao.common.get_input_data = function(input, callback, char_) {
+ var reader = new FileReader(),
+ file = input[0].files[0];
+ reader.onload = function() {
+ var value = new Uint8Array(reader.result);
+ if (char_) {
+ value = String.fromCharCode.apply(null, value);
+ }
+ callback(value, file.name);
+ };
+ reader.readAsArrayBuffer(file);
+ };
+
Sao.common.ellipsize = function(string, length) {
if (string.length <= length) {
return string;
diff --git a/src/sao.less b/src/sao.less
index a9201e85f4..bd0e8d7f87 100644
--- a/src/sao.less
+++ b/src/sao.less
@@ -124,6 +124,14 @@ input[type='color'] {
}
}
+.input-file {
+ & input[type='file'] {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ }
+}
+
/* Hide empty image displayed as broken image */
[src=''] {
visibility: hidden;
diff --git a/src/tab.js b/src/tab.js
index 37c6c270cd..ee6e4007fc 100644
--- a/src/tab.js
+++ b/src/tab.js
@@ -72,6 +72,7 @@
icon: 'tryton-attach',
label: Sao.i18n.gettext('Attachment'),
tooltip: Sao.i18n.gettext('Add an attachment to the record'),
+ dropdown: true,
}, {
id: 'note',
icon: 'tryton-note',
@@ -208,16 +209,37 @@
'role': 'group'
}).appendTo(toolbar.find('.btn-toolbar'));
}
- this.buttons[item.id] = jQuery('', {
+ var attributes = {
'type': 'button',
'class': 'btn btn-default navbar-btn',
'title': item.label,
'id': item.id
- })
- .append(Sao.common.ICONFACTORY.get_icon_img(item.icon, {
- 'aria-hidden': 'true',
- }))
- .appendTo(group);
+ };
+ if (item.dropdown) {
+ attributes['class'] += ' dropdown-toggle';
+ attributes['data-toggle'] = 'dropdown';
+ attributes['aria-expanded'] = false;
+ attributes['aria-haspopup'] = true;
+ }
+ var button = jQuery('', attributes)
+ .append(Sao.common.ICONFACTORY.get_icon_img(item.icon, {
+ 'aria-hidden': 'true',
+ }));
+ this.buttons[item.id] = button;
+ if (item.dropdown) {
+ var dropdown = jQuery('
', {
+ 'class': 'btn-group dropdown',
+ 'role': 'group',
+ }).append(button.append(jQuery('', {
+ 'class': 'caret',
+ }))).append(jQuery('', {
+ 'class': 'dropdown-menu',
+ 'role': 'menu',
+ 'aria-labelledby': item.id,
+ })).appendTo(group);
+ } else {
+ button.appendTo(group);
+ }
this.buttons[item.id].click(item, function(event) {
var item = event.data;
var button = this.buttons[item.id];
@@ -234,8 +256,17 @@
}).appendTo(jQuery('', {
'class': 'navbar-text hidden-xs',
}).insertAfter(this.buttons.previous));
- toolbar.find('.btn-toolbar > .btn-group').last().addClass(
- 'hidden-xs');
+ toolbar.find('.btn-toolbar > .btn-group').last()
+ .addClass( 'hidden-xs')
+ .find('.dropdown')
+ .on('show.bs.dropdown', function() {
+ jQuery(this).parents('.btn-group')
+ .removeClass( 'hidden-xs');
+ })
+ .on('hide.bs.dropdown', function() {
+ jQuery(this).parents('.btn-group')
+ .addClass('hidden-xs');
+ });
var tabs = jQuery('#tabs');
toolbar.affix({
'target': tabs.parent(),
@@ -487,7 +518,7 @@
Sao.i18n.gettext('Print report')]
].forEach(function(menu_action) {
var button = jQuery('', {
- 'class': 'btn-group',
+ 'class': 'btn-group dropdown',
'role': 'group'
})
.append(jQuery('', {
@@ -521,7 +552,7 @@
jQuery(this).parents('.btn-group').addClass(
'hidden-xs');
});
- var menu = button.find('ul[role*="menu"]');
+ var menu = button.find('.dropdown-menu');
button.click(function() {
menu.find('.' + menu_action[0] + '_button').remove();
var buttons = screen.get_buttons();
@@ -886,14 +917,77 @@
}.bind(this));
}
},
- attach: function() {
- var record = this.screen.current_record;
- if (!record || (record.id < 0)) {
+ attach: function(evt) {
+ var window_ = function() {
+ return new Sao.Window.Attachment(record, function() {
+ this.update_attachment_count(true);
+ }.bind(this));
+ }.bind(this);
+ var dropdown = this.buttons.attach.parents('.dropdown');
+ if (!evt) {
+ window.setTimeout(function() {
+ this.buttons.attach.click();
+ }.bind(this));
return;
}
- new Sao.Window.Attachment(record, function() {
- this.update_attachment_count(true);
- }.bind(this));
+ var record = this.screen.current_record;
+ var menu = dropdown.find('.dropdown-menu');
+ menu.children().remove();
+ Sao.Window.Attachment.get_attachments(record)
+ .then(function(attachments) {
+ attachments.forEach(function(value) {
+ var name = value[0],
+ callback = value[1];
+ var link = jQuery('', {
+ 'role': 'menuitem',
+ 'href': '#',
+ 'tabindex': -1,
+ }).append(name).appendTo(jQuery('', {
+ 'role': 'presentation',
+ }).appendTo(menu));
+ if (typeof callback == 'string') {
+ link.attr('href', callback);
+ link.attr('target', '_new');
+ } else {
+ link.click(function(evt) {
+ evt.preventDefault();
+ callback();
+ });
+ }
+ });
+ }).always(function() {
+ menu.append(jQuery('', {
+ 'class': 'divider',
+ }));
+ menu.append(jQuery('', {
+ 'role': 'presentation',
+ 'class': 'input-file',
+ }).append(jQuery('', {
+ 'type': 'file',
+ 'role': 'menuitem',
+ 'tabindex': -1,
+ }).change(function() {
+ Sao.common.get_input_data(
+ jQuery(this), function(data, filename) {
+ window_().add_data(data, filename);
+ });
+ })).append(jQuery('', {
+ 'role': 'menuitem',
+ 'href': '#',
+ 'tabindex': -1,
+ }).append(Sao.i18n.gettext('Add...'))));
+ menu.append(jQuery('', {
+ 'role': 'presentation',
+ }).append(jQuery('', {
+ 'role': 'menuitem',
+ 'href': '#',
+ 'tabindex': -1,
+ }).append(Sao.i18n.gettext('Manage...'))
+ .click(function(evt) {
+ evt.preventDefault();
+ window_();
+ })));
+ });
},
update_attachment_count: function(reload) {
var record = this.screen.current_record;
diff --git a/src/view/form.js b/src/view/form.js
index 08b37ad4a9..54d0032665 100644
--- a/src/view/form.js
+++ b/src/view/form.js
@@ -3512,14 +3512,17 @@ function eval_pyson(value){
).appendTo(group);
this.but_save_as.click(this.save_as.bind(this));
- this.but_select = jQuery('', {
- 'class': 'btn btn-default',
+ this.input_select = jQuery('', {
+ 'type': 'file',
+ }).change(this.select.bind(this));
+ this.but_select = jQuery('', {
+ 'class': 'btn btn-default input-file',
'type': 'button',
'aria-label': Sao.i18n.gettext("Select"),
'title': Sao.i18n.gettext("Select..."),
- }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-search')
+ }).append(this.input_select
+ ).append(Sao.common.ICONFACTORY.get_icon_img('tryton-search')
).appendTo(group);
- this.but_select.click(this.select.bind(this));
this.but_clear = jQuery('', {
'class': 'btn btn-default',
@@ -3533,9 +3536,11 @@ function eval_pyson(value){
return group;
},
filename_field: function() {
- var record = this.record();
- if (record) {
- return record.model.fields[this.filename];
+ if (this.filename) {
+ var record = this.record();
+ if (record) {
+ return record.model.fields[this.filename];
+ }
}
},
update_buttons: function(value) {
@@ -3550,58 +3555,16 @@ function eval_pyson(value){
}
},
select: function() {
- var record = this.record();
+ var record = this.record(),
+ field = this.field(),
+ filename_field = this.filename_field();
- var close = function() {
- file_dialog.modal.on('hidden.bs.modal', function(event) {
- jQuery(this).remove();
- });
- file_dialog.modal.modal('hide');
- };
-
- var save_file = function() {
- var reader = new FileReader();
- reader.onload = function(evt) {
- var field = this.field();
- var uint_array = new Uint8Array(reader.result);
- var value;
- if (field.get_size) {
- value = uint_array;
- } else {
- value = String.fromCharCode.apply(null, uint_array);
- }
- field.set_client(record, value);
- }.bind(this);
- reader.onloadend = function(evt) {
- close();
- };
- var file = file_selector[0].files[0];
- reader.readAsArrayBuffer(file);
- if (this.filename) {
- this.filename_field().set_client(record, file.name);
+ Sao.common.get_input_data(this.input_select, function(data, filename) {
+ field.set_client(record, data);
+ if (filename_field) {
+ filename_field.set_client(record, filename);
}
- }.bind(this);
-
- var file_dialog = new Sao.Dialog(
- Sao.i18n.gettext('Select'), 'file-dialog');
- file_dialog.footer.append(jQuery('', {
- 'class': 'btn btn-link',
- 'type': 'button'
- }).append(Sao.i18n.gettext('Cancel')).click(close))
- .append(jQuery('', {
- 'class': 'btn btn-primary',
- 'type': 'submit'
- }).append(Sao.i18n.gettext('OK')).click(save_file));
- file_dialog.content.submit(function(e) {
- save_file();
- e.preventDefault();
- });
-
- var file_selector = jQuery('', {
- type: 'file'
- }).appendTo(file_dialog.body);
-
- file_dialog.modal.modal('show');
+ }, !field.get_size);
},
open: function() {
var params = {};
diff --git a/src/window.js b/src/window.js
index cf8ceeb44b..a69cba8360 100644
--- a/src/window.js
+++ b/src/window.js
@@ -55,23 +55,12 @@
this.info_bar = new Sao.Window.InfoBar();
var view_type = kwargs.view_type || 'form';
- var form_prm = jQuery.when();
- var screen_views = [];
- for (var i = 0, len = this.screen.views.length; i < len; i++) {
- screen_views.push(this.screen.views[i].view_type);
- }
- if (!~screen_views.indexOf(view_type) &&
- !~this.screen.view_to_load.indexOf(view_type)) {
- form_prm = this.screen.add_view_id(null, view_type);
- }
-
- var switch_prm = form_prm.then(function() {
- return this.screen.switch_view(view_type).done(function() {
+ this.switch_prm = this.screen.switch_view(view_type)
+ .done(function() {
if (kwargs.new_) {
this.screen.new_(undefined, kwargs.rec_name);
}
}.bind(this));
- }.bind(this));
var dialog = new Sao.Dialog('', 'window-form', 'lg', false);
this.el = dialog.modal;
this.el.on('keydown', function(e) {
@@ -231,7 +220,7 @@
dialog.body.append(this.info_bar.el);
- switch_prm.done(function() {
+ this.switch_prm.done(function() {
title_prm.done(dialog.add_title.bind(dialog));
content.append(this.screen.screen_container.alternate_viewport);
this.el.modal('show');
@@ -433,14 +422,14 @@
mode: ['tree', 'form'],
context: context,
});
- screen.switch_view().done(function() {
- screen.search_filter();
- });
var title = record.rec_name().then(function(rec_name) {
return Sao.i18n.gettext('Attachments (%1)', rec_name);
});
Sao.Window.Attachment._super.init.call(this, screen, this.callback,
{view_type: 'tree', title: title});
+ this.switch_prm = this.switch_prm.then(function() {
+ return screen.search_filter();
+ });
},
callback: function(result) {
var prm = jQuery.when();
@@ -450,8 +439,65 @@
if (this.attachment_callback) {
prm.always(this.attachment_callback.bind(this));
}
- }
+ },
+ add_data: function(data, filename) {
+ var screen = this.screen;
+ this.switch_prm.then(function() {
+ screen.set_current_record(null);
+ screen.switch_view('form').then(function() {
+ screen.new_().then(function(record) {
+ var data_field = record.model.fields.data;
+ record.field_set_client(
+ data_field.description.filename, filename);
+ record.field_set_client('data', data);
+ screen.display();
+ });
+ });
+ });
+ },
});
+ Sao.Window.Attachment.get_attachments = function(record) {
+ var prm;
+ if (record && (record.id >= 0)) {
+ var context = record.get_context();
+ prm = Sao.rpc({
+ 'method': 'model.ir.attachment.search_read',
+ 'params': [
+ [['resource', '=', record.model.name + ',' + record.id]],
+ 0, 20, null, ['rec_name', 'name', 'type', 'link'],
+ context],
+ }, record.model.session);
+ } else {
+ prm = jQuery.when([]);
+ }
+ var partial = function(callback, attachment, context, session) {
+ return function() {
+ return callback(attachment, context, session);
+ };
+ };
+ return prm.then(function(attachments) {
+ return attachments.map(function(attachment) {
+ var name = attachment.rec_name;
+ if (attachment.type == 'link') {
+ return [name, attachment.link];
+ } else {
+ var callback = Sao.Window.Attachment[
+ 'open_' + attachment.type];
+ return [name, partial(
+ callback, attachment, context, record.model.session)];
+ }
+ });
+ });
+ };
+ Sao.Window.Attachment.open_data = function(attachment, context, session) {
+ Sao.rpc({
+ 'method': 'model.ir.attachment.read',
+ 'params': [
+ [attachment.id], ['data'], context],
+ }, session).then(function(values) {
+ Sao.common.download_file(values[0].data, attachment.name);
+ });
+ };
Sao.Window.Note = Sao.class_(Sao.Window.Form, {
init: function(record, callback) {