[MERGE] Improved search view UI

This branch is the result of a UI rethink of the search views.
In order to do that, many other parts had to be moved/reworked.
Main changes affect action manager, view manager and search view
(they have been almost completely rewritten)

Some highlights:
* search view has 3 submenus (filters, groupbys, favorites) and all inputs are included in these submenus
* view manager header is now responsive, and bootstrapized
* icons are now using fontawesome
* transitions between views should have much less flicker (because of work in action and view managers)
* debug mode has a pretty icon (because why not)
* we can now add custom groupbys (every groupable fields from the model are exposed (make sure you have good descriptive strings))
This commit is contained in:
Géry Debongnie
2014-10-21 12:07:58 +02:00
committed by Olivier Dony
33 changed files with 2363 additions and 3565 deletions
+19 -3
View File
@@ -1,3 +1,4 @@
@charset "UTF-8";
.openerp .oe_dashboard_layout_1 .oe_dashboard_column.index_0 {
width: 100%;
}
@@ -82,7 +83,7 @@
color: #b3b3b3;
}
.openerp .oe_dashboard .oe_action .oe_header .oe_icon:hover {
color: #666666;
color: #666;
text-decoration: none;
}
.openerp .oe_dashboard .oe_action .oe_header .oe_close:after {
@@ -116,8 +117,14 @@
.openerp .oe_dashboard .oe_action .oe_content {
padding: 0 12px 12px 12px;
}
.openerp .oe_dashboard .oe_action .oe_content .oe_view_manager_header {
display: none;
.openerp .oe_dashboard .oe_action .oe_content .oe-view-manager-views {
position: relative;
}
.openerp .oe_dashboard .oe_action .oe_content .oe-view-manager {
display: block;
}
.openerp .oe_dashboard .oe_action .oe_content .oe-view-manager-content {
display: block;
}
.openerp .oe_dashboard .oe_action .oe_content .oe_list_content > thead {
border-bottom: 1px;
@@ -125,3 +132,12 @@
.openerp .oe_dashboard .oe_action .oe_content .oe_list_content > tbody tr:nth-child(odd) {
background: transparent;
}
.openerp .oe-search-options .add-to-dashboard button {
margin: 3px 25px;
display: none;
}
.openerp .oe-search-options .add-to-dashboard input {
width: calc(100% - 50px);
margin: 3px 25px;
display: none;
}
+16 -17
View File
@@ -98,25 +98,14 @@
margin-left: 4px
padding: 0 4px 0 4px
height: 16px !important
//.oe_rename
//float: left
//padding-right: 4px
//position: relative
//top: 1px
//.oe_input
//height: 16px;
//position: relative;
//top: 2px;
//.ui-sortable-placeholder
//border: 1px dotted black;
//visibility: visible !important;
//height: 50px !important;
//*
//visibility: hidden;
.oe_content
padding: 0 12px 12px 12px
.oe_view_manager_header
display: none
.oe-view-manager-views
position: relative
.oe-view-manager
display: block
.oe-view-manager-content
display: block
.oe_list_content
> thead
border-bottom: 1px
@@ -124,3 +113,13 @@
tr:nth-child(odd)
background: transparent
.oe-search-options
.add-to-dashboard
button
margin: 3px 25px
display: none
input
width: calc(100% - 50px)
margin: 3px 25px
display: none
+72 -98
View File
@@ -8,6 +8,16 @@ if (!instance.board) {
}
instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
events: {
'click .oe_dashboard_link_reset': 'on_reset',
'click .oe_dashboard_link_change_layout': 'on_change_layout',
'click h2.oe_header span.oe_header_txt': function (ev) {
if(ev.target === ev.currentTarget)
self.on_header_string($(ev.target).parent());
},
'click .oe_dashboard_column .oe_fold': 'on_fold_action',
'click .oe_dashboard_column .oe_close': 'on_close_action',
},
init: function(view, node) {
this._super(view, node);
this.form_template = 'DashBoard';
@@ -18,7 +28,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
var self = this;
this._super.apply(this, arguments);
this.$el.find('.oe_dashboard_column').sortable({
this.$('.oe_dashboard_column').sortable({
connectWith: '.oe_dashboard_column',
handle: '.oe_header',
scroll: false
@@ -27,16 +37,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
var old_title = this.__parentedParent.get('title');
this.__parentedParent.on('load_record', self, function(){
self.__parentedParent.set({ 'title': old_title});
})
// Events
this.$el.find('.oe_dashboard_link_reset').click(this.on_reset);
this.$el.find('.oe_dashboard_link_change_layout').click(this.on_change_layout);
this.$el.find('h2.oe_header span.oe_header_txt').click(function(ev){
if(ev.target === ev.currentTarget)
self.on_header_string($(ev.target).parent());
});
this.$el.delegate('.oe_dashboard_column .oe_fold', 'click', this.on_fold_action);
this.$el.delegate('.oe_dashboard_column .oe_close', 'click', this.on_close_action);
// Init actions
_.each(this.node.children, function(column, column_index) {
@@ -208,6 +209,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
views_switcher : false,
action_buttons : false,
pager: false,
headless: true,
low_profile: true,
display_title: false,
list: {
@@ -244,7 +246,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
};
var list = am.inner_widget.views.list;
if (list) {
list.deferred.done(function() {
list.created.done(function() {
$(list.controller.groups).off('row_link').on('row_link', function(e, id) {
new_form_action(id);
});
@@ -252,7 +254,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
}
var kanban = am.inner_widget.views.kanban;
if (kanban) {
kanban.deferred.done(function() {
kanban.created.done(function() {
kanban.controller.open_record = function(id, editable) {
new_form_action(id, editable);
};
@@ -326,113 +328,85 @@ instance.web.form.tags.add('vpaned', 'instance.web.form.DashBoardLegacy');
instance.web.form.tags.add('board', 'instance.web.form.DashBoard');
instance.board.AddToDashboard = instance.web.search.Input.extend({
template: 'SearchView.addtodashboard',
_in_drawer: true,
init: function (parent) {
this._super(parent);
this.is_loaded = null;
},
start: function () {
instance.web.search.FavoriteMenu.include({
prepare_dropdown_menu: function (filters) {
var self = this;
this.$el
.on('click', 'h4', this.proxy('load_and_show_option'))
.on('submit', 'form', function (e) {
e.preventDefault();
self.add_dashboard();
});
return $.when();
},
load_data:function(){
var board = new instance.web.Model('board.board');
return board.call('list', [board.context()]);
},
_x:function() {
if (!instance.webclient) { return $.Deferred().reject(); }
var dashboard_menu = instance.webclient.menu.data.data.children;
return new instance.web.Model('ir.model.data')
.query(['res_id'])
.filter([['name','=','menu_reporting_dashboard']])
.first().then(function (result) {
var menu = _(dashboard_menu).chain()
.pluck('children')
.flatten(true)
.find(function (child) { return child.id === result.res_id; })
.value();
return menu ? menu.children : [];
this._super(filters);
this.$('.favorites-menu').append(QWeb.render('SearchView.addtodashboard'));
var $add_to_dashboard = this.$('.add-to-dashboard');
this.$add_dashboard_btn = $add_to_dashboard.eq(2).find('button');
this.$add_dashboard_input = $add_to_dashboard.eq(1).find('input');
this.$add_dashboard_link = $add_to_dashboard.first().find('a');
var title = this.searchview.getParent().title;
this.$add_dashboard_input.val(title);
this.$add_dashboard_link.click(function () {
self.toggle_dashboard_menu();
});
this.$add_dashboard_btn.click(this.proxy('add_dashboard'));
},
render_data: function(dashboard_choices){
var selection = instance.web.qweb.render(
"SearchView.addtodashboard.selection", {
selections: dashboard_choices});
this.$("input").before(selection)
toggle_dashboard_menu: function (is_open) {
this.$add_dashboard_link
.toggleClass('closed-menu', !is_open)
.toggleClass('open-menu', is_open);
this.$add_dashboard_btn.toggle(is_open);
this.$add_dashboard_input.toggle(is_open);
if (this.$add_dashboard_link.hasClass('open-menu')) {
this.$add_dashboard_input.focus();
}
},
add_dashboard: function(){
var self = this;
if (! this.view.view_manager.action || ! this.$el.find("select").val()) {
close_menus: function () {
this.toggle_dashboard_menu(false);
this._super();
},
add_dashboard: function () {
var self = this,
view_manager = this.findAncestor(function (a) {
return a instanceof instance.web.ViewManager
});
if (!view_manager.action) {
this.do_warn(_t("Can't find dashboard action"));
return;
}
var data = this.view.build_search_data();
var context = new instance.web.CompoundContext(this.view.dataset.get_context() || []);
var domain = new instance.web.CompoundDomain(this.view.dataset.get_domain() || []);
var searchview = view_manager.searchview,
data = searchview.build_search_data(),
context = new instance.web.CompoundContext(searchview.dataset.get_context() || []),
domain = new instance.web.CompoundDomain(searchview.dataset.get_domain() || []);
_.each(data.contexts, context.add, context);
_.each(data.domains, domain.add, domain);
context.add({
group_by: instance.web.pyeval.eval('groupbys', data.groupbys || [])
});
var c = instance.web.pyeval.eval('context', context);
for(var k in c) {
if (c.hasOwnProperty(k) && /^search_default_/.test(k)) {
delete c[k];
}
}
// TODO: replace this 6.1 workaround by attribute on <action/>
this.toggle_dashboard_menu(false);
c.dashboard_merge_domains_contexts = false;
var d = instance.web.pyeval.eval('domain', domain);
this.rpc('/board/add_to_dashboard', {
menu_id: this.$el.find("select").val(),
action_id: this.view.view_manager.action.id,
context_to_save: c,
domain: d,
view_mode: this.view.view_manager.active_view,
name: this.$el.find("input").val()
}).done(function(r) {
if (r === false) {
self.do_warn(_t("Could not add filter to dashboard"));
} else {
self.$el.toggleClass('oe_opened');
self.do_notify(_t("Filter added to dashboard"), '');
}
});
var d = instance.web.pyeval.eval('domain', domain),
board = new instance.web.Model('board.board'),
name = self.$add_dashboard_input.val();
board.call('list', [board.context()])
.then(function (board_list) {
return self.rpc('/board/add_to_dashboard', {
menu_id: board_list[0].id,
action_id: view_manager.action.id,
context_to_save: c,
domain: d,
view_mode: view_manager.active_view.type,
name: name,
});
}).then(function (r) {
if (r) {
self.do_notify(_.str.sprintf(_t("'%s' added to dashboard"), name), '');
} else {
self.do_warn(_t("Could not add filter to dashboard"));
}
});
},
load_and_show_option: function(){
if (!this.is_loaded) {
this.is_loaded = this.load_data().done(this.proxy("render_data"));
}
this.is_loaded.done(this.proxy('show_option'));
},
show_option: function () {
this.$el.toggleClass('oe_opened');
if (! this.$el.hasClass('oe_opened'))
return;
this.$("input").val(this.view.fields_view.name || "" );
},
});
instance.web.SearchViewDrawer.include({
add_common_inputs: function() {
this._super();
var vm = this.getParent().getParent();
if (vm.inner_action && vm.inner_action.id && vm.inner_action.views) {
(new instance.board.AddToDashboard(this));
}
}
});
};
+4 -14
View File
@@ -63,19 +63,9 @@
<iframe width="100%" frameborder="0" t-att-src="url"/>
</t>
<div t-name="SearchView.addtodashboard" class="oe_searchview_dashboard">
<h4>Add to Dashboard</h4>
<form>
<p><input placeholder="Title of new dashboard item"/></p>
<button class="oe_apply" type="submit">Add</button>
</form>
</div>
<t t-name="SearchView.addtodashboard.selection">
<select>
<option t-foreach="selections" t-as="element"
t-att-value="element.id ">
<t t-esc="element.name"/></option>
</select>
<t t-name="SearchView.addtodashboard">
<li class="add-to-dashboard closed-menu"><a>Add to my Dashboard</a></li>
<li class="add-to-dashboard"><input class="form-control" type="text"/></li>
<li class="add-to-dashboard"><button class="btn btn-default">Add</button></li>
</t>
</template>
@@ -12,20 +12,6 @@
</field>
</record>
<record model="ir.ui.view" id="res_partner_opt_out_search">
<field name="name">res.partner.opt_out.search</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<filter string="Suppliers" position="after">
<separator />
<filter string="Available for mass mailing"
name='not_opt_out' domain="[('opt_out', '=', False)]"
help="Partners that did not ask not to be included in mass mailing campaigns" />
</filter>
</field>
</record>
<!--Definition of an email template with an empty body that will be used in partner mailing. Used to give a
basis for email recipients, name and to ease the definition of a further elaborated template. -->
<record id="email_template_partner" model="email.template">
@@ -1,54 +1,50 @@
openerp.google_spreadsheet = function(instance) {
var _t = instance.web._t;
var _t = instance.web._t,
QWeb = instance.web.qweb;
instance.web.FormView.include({
on_processed_onchange: function(result, processed) {
var self = this;
var fields = self.fields;
_(result.selection).each(function (selection, fieldname) {
var field = fields[fieldname];
if (!field) { return; }
field.field.selection = selection;
field.values = selection;
field.renderElement();
});
return this._super(result, processed);
},
on_processed_onchange: function(result, processed) {
var self = this;
var fields = self.fields;
_(result.selection).each(function (selection, fieldname) {
var field = fields[fieldname];
if (!field) { return; }
field.field.selection = selection;
field.values = selection;
field.renderElement();
});
return this._super(result, processed);
},
});
instance.web.search.FavoriteMenu.include({
prepare_dropdown_menu: function (filters) {
this._super(filters);
this.$('.favorites-menu').append(QWeb.render('SearchView.addtogooglespreadsheet'));
this.$('.add-to-spreadsheet').click(this.add_to_spreadsheet.bind(this));
},
add_to_spreadsheet: function () {
var data = this.searchview.build_search_data(),
model = this.searchview.dataset.model,
view_manager = this.searchview.getParent(),
list_view = view_manager.views.list,
list_view_id = list_view ? list_view.view_id : false,
context = this.searchview.dataset.get_context() || [],
domain = this.searchview.dataset.get_domain() || [],
compound_context = new instance.web.CompoundContext(context),
compound_domain = new instance.web.CompoundDomain(context),
groupbys = instance.web.pyeval.eval('groupbys', data.groupbys).join(" "),
ds = new instance.web.DataSet(this, 'google.drive.config');
_.each(data.contexts, compound_context.add, compound_context);
_.each(data.domains, compound_domain.add, compound_domain);
compound_domain = JSON.stringify(compound_domain.eval());
ds.call('set_spreadsheet', [model, compound_domain, groupbys, list_view_id])
.done(function (res) {
if (res['url']){
window.open(res['url'], '_blank');
}
});
},
});
instance.board.AddToGoogleSpreadsheet = instance.web.search.Input.extend({
template: 'SearchView.addtogooglespreadsheet',
_in_drawer: true,
start: function () {
var self = this;
this.$el.on('click', 'h4', function(){
var view = self.view;
var data = view.build_search_data();
var model = view.model;
var list_view = self.view.getParent().views['list'];
var view_id = list_view ? list_view.view_id : false;
var context = new instance.web.CompoundContext(view.dataset.get_context() || []);
var domain = new instance.web.CompoundDomain(view.dataset.get_domain() || []);
_.each(data.contexts, context.add, context);
_.each(data.domains, domain.add, domain);
domain = JSON.stringify(domain.eval());
var groupbys = instance.web.pyeval.eval('groupbys', data.groupbys).join(" ");
var view_id = view_id;
var ds = new instance.web.DataSet(self, 'google.drive.config');
ds.call('set_spreadsheet', [model, domain, groupbys, view_id]).done(function (res) {
if (res['url']){
window.open(res['url'], '_blank');
}
});
});
},
});
instance.web.SearchViewDrawer.include({
add_common_inputs: function() {
this._super();
var vm = this.getParent().getParent();
if (vm.inner_action && vm.inner_action.views) {
(new instance.board.AddToGoogleSpreadsheet(this));
}
}
});
};
@@ -1,5 +1,5 @@
<template>
<div t-name="SearchView.addtogooglespreadsheet" class="oe_searchview_dashboard">
<h4>Add to Google Spreadsheet</h4>
</div>
<t t-name="SearchView.addtogooglespreadsheet">
<li><a class="add-to-spreadsheet">Add to Google Spreadsheet</a></li>
</t>
</template>
+3
View File
@@ -740,3 +740,6 @@
margin-bottom: 10px;
}
.oe_mail_wall .oe-groupby-menu {
display: none;
}
+8 -4
View File
@@ -1278,6 +1278,7 @@ openerp.mail = function (session) {
start: function () {
this._super.apply(this, arguments);
this.bind_events();
return $.when();
},
/* instantiate the compose message object and insert this on the DOM.
@@ -1967,11 +1968,14 @@ openerp.mail = function (session) {
* @param {Object} defaults ??
*/
load_searchview: function (defaults) {
var ds_msg = new session.web.DataSetSearch(this, 'mail.message');
this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, false);
var self = this,
ds_msg = new session.web.DataSetSearch(this, 'mail.message'),
options = { $buttons: this.$('.oe-search-options') };
this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, options);
this.searchview.on('search_data', this, this.do_searchview_search);
this.searchview.appendTo(this.$('.oe_view_manager_view_search'),
this.$('.oe_searchview_drawer_container'));
this.searchview.appendTo(this.$('.oe-view-manager-search-view')).then(function () {
self.searchview.toggle_visibility(true);
});
if (this.searchview.has_defaults) {
this.searchview.ready.then(this.searchview.do_search);
}
+19 -30
View File
@@ -166,36 +166,25 @@
wall main template
Template used to display the communication history in the wall.
-->
<div t-name="mail.wall" class="oe_view_manager oe_mail_wall oe_view_manager_current">
<table class="oe_view_manager_header">
<colgroup>
<col width="33%"/>
<col width="33%"/>
<col width="34%"/>
</colgroup>
<tbody>
<tr class="oe_header_row oe_header_row_top">
<td colspan="2">
<h2 class="oe_view_title">
<span class="oe_view_title_text">
<t t-esc="widget.action.name"/>
</span>
</h2>
<t t-if="widget.action.params.header_description">
<br/>
<span class="oe_view_subtitle_text">
<t t-raw="widget.action.params.header_description"/>
</span>
</t>
</td>
<td><div class="oe_view_manager_view_search"/></td>
</tr>
</tbody>
</table>
<div class="oe_view_manager_wrapper">
<div>
<div class="oe_view_manager_body">
<div class="oe_searchview_drawer_container"/>
<div t-name="mail.wall" class="oe-view-manager oe_mail_wall oe_view_manager_current">
<div class="oe-view-manager-header container-fluid">
<div class="row">
<div class="col-md-6 oe-header-title">
<ol class="oe-view-title breadcrumb">
<li class="active"><t t-esc="widget.action.name"/></li>
</ol>
</div>
<div class="oe-view-manager-search-view col-md-6" />
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-6">
<div class="oe-search-options btn-group"/>
</div>
</div>
</div>
<div class="oe-view-manager-content">
<div class="oe-view-manager-body">
<div>
<div class="oe_mail-placeholder"></div>
<aside class="oe_mail_wall_aside"></aside>
</div>
+1 -1
View File
@@ -65,7 +65,7 @@ class crm_case_section(osv.osv):
_columns = {
'name': fields.char('Sales Team', size=64, required=True, translate=True),
'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True),
'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True, string="Full Name"),
'code': fields.char('Code', size=8),
'active': fields.boolean('Active', help="If the active field is set to "\
"true, it will allow you to hide the sales team without removing it."),
+1 -1
View File
@@ -94,7 +94,7 @@ openerp.share = function(session) {
}
});
session.web.ViewManagerAction.include({
session.web.ViewManager.include({
start: function() {
var self = this;
this.check_if_action_is_defined();
File diff suppressed because it is too large Load Diff
+256 -471
View File
@@ -267,7 +267,7 @@ $sheet-padding: 16px
font-size: inherit
// }}}
// Button style {{{
a.button:link, a.button:visited, button, .oe_button, input[type='submit']
a.button:link, a.button:visited, .oe_button, input[type='submit']
display: inline-block
border: 1px solid rgba(0,0,0,0.4)
color: #4c4c4c
@@ -282,22 +282,22 @@ $sheet-padding: 16px
-webkit-font-smoothing: antialiased
outline: none
a.button:hover, button:hover,.oe_button:hover, input[type='submit']:hover
a.button:hover,.oe_button:hover, input[type='submit']:hover
@include vertical-gradient(#f6f6f6, #e3e3e3)
cursor: pointer
background-position: 0
a.button:focus, button:focus, .oe_button:focus, input[type='submit']:focus
a.button:focus, .oe_button:focus, input[type='submit']:focus
border: 1px solid #80bfff
background-position: 0
@include vertical-gradient(#f6f6f6, #e3e3e3)
@include box-shadow((0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, .8) inset))
a.button:active, a.button.active, button:active, .oe_button:active, .oe_button.active, input[type='submit']:active, input[type='submit'].active
a.button:active, a.button.active, .oe_button:active, .oe_button.active, input[type='submit']:active, input[type='submit'].active
@include vertical-gradient(#e3e3e3, #f6f6f6)
@include box-shadow(none)
a.button.disabled, button:disabled, .oe_button:disabled, input[type='submit']:disabled
a.button.disabled, .oe_button:disabled, input[type='submit']:disabled
background: #efefef !important
border: 1px solid #d1d1d1 !important
@include box-shadow(none !important)
@@ -653,44 +653,6 @@ $sheet-padding: 16px
border-right: 4px solid transparent
border-top: 4px solid#404040
@include opacity(0.5)
.oe_sidebar
white-space: nowrap
.oe_dropdown_menu
.oe_sidebar_add_attachment
height: 20px
cursor: pointer
padding-left: 6px
margin-top: 6px
span
font-weight: bold
.oe_hidden_input_file
width: 200px
&:hover
@include vertical-gradient(#f0f0fa, #eeeef6)
@include box-shadow(none)
li
.oe_sidebar_delete_item
position: absolute
top: 4px
right: 4px
display: none
width: 12px
height: 12px
padding: 1px
color: #8786b7
line-height: 8px
text-align: center
font-weight: bold
text-shadow: 0 1px 1px white
&:hover
text-decoration: none
color: white
background: #8786b7
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4)
@include radius(2px)
&:hover
.oe_sidebar_delete_item
display: inline-block
// }}}
// Loading {{{
.oe_loading
@@ -899,202 +861,148 @@ $sheet-padding: 16px
.oe_application
width: 100%
height: 100%
a
color: $link-color
overflow: hidden
> div
position: relative
height: 100%
> .oe_view_manager
> .oe_view_manager_wrapper
display: table-row
height: 100%
> div
position: relative
height: 100%
> .oe_view_manager_body
position: absolute
position: static\9
top: 0
bottom: 0
left: 0
right: 0
overflow: auto
.oe-view-manager
.oe-view-manager-debug
margin-right: 5px
.oe-view-manager-content
display: table-row
height: 100%
.oe-view-manager-body
position: relative
height: 100%
a
color: $link-color
.oe-view-manager-view-kanban:not(:empty)
height: 100%
> div
position: absolute
top: 0
bottom: 0
left: 0
right: 0
overflow: auto
.oe_form_field_one2many
.oe-view-manager-views
position: initial
.oe_breadcrumb_item:not(:last-child)
max-width: 7em
white-space: nowrap
text-overflow: ellipsis
.oe_breadcrumb_title > *
display: inline-block
overflow: hidden
font-weight: bold
// }}}
// ViewManager common {{{
.oe_view_manager
.oe-view-manager
display: table
height: inherit
width: 100%
.oe_view_manager_view_kanban:not(:empty)
height: 100%
&[data-view-type=kanban]
.oe_view_manager_body
display: table-row
table.oe_view_manager_header
border-collapse: separate
width: 100%
table-layout: fixed
.oe_header_row
//min-height: 26px
//line-height: 26px
clear: both
text-shadow: 0 1px 1px white
.oe_header_row:last-child
td
padding-top: 0
.oe_header_row:first-child
td
padding-top: 8px
.oe_view_manager_sidebar
margin: 0px auto
text-align: center
.oe_dropdown_arrow:after
opacity: 0.9
td
line-height: 26px
h2
font-size: 18px
margin: 0
float: left
line-height: 30px
a
color: $link-color
.oe_dropdown_menu
line-height: normal
.oe_button_group
display: inline-block
border: 1px solid #ababab
@include radius(5px)
li
float: left
border-right: 1px solid #ababab
&:last-child
border: none
a
color: #4c4c4c
&:hover
text-decoration: none
.active
background: #999
@include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset)
a
color: #fff
text-shadow: 0 1px 2px rgba(0,0,0,0.4)
.oe_view_manager_buttons
white-space: nowrap
// }}}
// ViewManager.switches {{{
.oe_view_manager_switch
padding: 0
margin: 0 0 0 8px
height: 100%
.oe-view-manager-header
display: table-row
background-color: rgb(240, 238, 238)
border-bottom: 1px solid #afafb6
-webkit-user-select: none
-moz-user-select: none
user-select: none
.dropdown-menu
li
margin: 0
width: 24px
height: 24px
line-height: 16px
padding: 0
text-align: center
list-style-type: none
position: relative
a
position: relative
.oe_vm_switch_list:after, .oe_vm_switch_tree:after
padding: 2px
content: "i"
.oe_vm_switch_form:after
content: "m"
.oe_vm_switch_graph:after
font-family: "mnmliconsRegular" !important
font-size: 21px
font-weight: 300 !important
content: "}"
top: -2px
position: relative
.oe_vm_switch_gantt:after
font-family: "mnmliconsRegular" !important
font-size: 21px
font-weight: 300 !important
content: "y"
top: -2px
position: relative
.oe_vm_switch_calendar:after
content: "P"
.oe_vm_switch_kanban:after
content: "k"
.oe_vm_switch_diagram:after
content: "f"
// }}}
// List pager {{{
.oe_list_pager
line-height: 26px
.oe_pager_value
float: left
margin-right: 8px
ul.oe_pager_group
padding: 0
margin: 0
.oe_pager_group
float: left
height: 24px
line-height: 24px
display: inline-block
border: 1px solid #ababab
cursor: pointer
@include radius(5px)
li
height: 24px
line-height: 24px
padding: 0
padding: 3px 25px
.oe-right-toolbar
float: right
> div
display: inline-block
.row
margin: 0
list-style-type: none
float: left
border-right: 1px solid #ababab
&:last-child
border: none
a
color: #4c4c4c
padding: 0 8px
&:hover
text-decoration: none
.active
background: #999
@include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset)
a
color: #fff
text-shadow: 0 1px 2px rgba(0,0,0,0.4)
.oe_list_pager.oe_list_pager_single_page .oe_pager_group
display: none
// }}}
// ViewManager application {{{
.oe_view_manager_current
height: 100%
> .oe_view_manager_header
border-bottom: 1px solid #cacaca
@include vertical-gradient(#fcfcfc, #dedede)
@include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1)))
.oe_header_row
td
padding: 8px
.oe_header_row:first-child
td
padding-top: 8px
// }}}
// ViewManager inline {{{
.row:first-child
padding-top: 3px
padding-bottom: 3px
.row:last-child
padding-bottom: 10px
.oe_tag
@include radius(0px)
.oe-view-title
font-size: 18px
padding-left: 0
margin: 0
background-color: rgb(240, 238, 238)
li
-moz-user-select: initial
-webkit-user-select: initial
user-select: initial
.oe-view-manager-search-view
padding-top: 5px
.oe-view-manager-switch
.oe-vm-switch-kanban:before
content: "\f009"
.oe-vm-switch-list:before
content: ""
.oe-vm-switch-form:before
content: "\f044"
.oe-vm-switch-graph:before
content: "\f080"
.oe-vm-switch-calendar:before
content: "\f073"
.oe-vm-switch-gantt:before
content: "\f0ae"
.oe-vm-switch-pivot:before
content: "\f0ce"
.oe-view-manager-buttons
display: inline-block
> div
display: none
.oe-view-manager-sidebar
display: inline-block
float: right
.oe_form_binary_form
cursor: pointer
span
padding: 3px 20px
input.oe_form_binary_file
width: 100%
.oe_form_binary_form:hover
background-color: #f5f5f5
.oe_file_attachment
padding: 3px 20px
display: inline-block
.oe_sidebar_delete_item
padding: 0
display: inline-block
.dropdown-menu
li
a
width: 100%
.oe_form_buttons
padding: 0
.oe-pager-buttons
min-height: 30px
.oe_view_manager_inline, .oe_view_manager_inlineview
height: 100%
> .oe_view_manager_header
> .oe-view-manager-header
display: none
// }}}
.oe-search-options
.selected
display: block
a
font-weight: bold
a:before
font-family: FontAwesome
position: absolute
left: 6px
top: 3px
content: "\f00c"
.oe_application
> div
> div
> .oe-view-manager-body
position: absolute
top: 90px
bottom: 0
left: 0
right: 0
overflow: auto
// }}}
// FormPopup {{{
.oe_popup_form
display: table
@@ -1106,98 +1014,53 @@ $sheet-padding: 16px
// End of customize
.oe_popup_list_pager
float: right
.oe_popup_search
width: 50%
float: right
// }}}
// SearchView {{{
.oe_searchview
cursor: text
position: relative
float: right
padding: 1px 0
line-height: 18px
min-width: 400px
border: 1px solid #ababab
background: white
@include radius(13px)
@include box-shadow(0 1px 2px rgba(0,0,0,0.2) inset)
input, textarea
padding: 3px
height: 14px
font-size: 12px
line-height: 18px
//Customize searchview input, select
input:not([type]), input[type="text"], input[type="number"]
width: 156px
height: 22px
input[type="checkbox"]
margin: 3px 3px 3px 4px
select
margin: 2px 4px 2px 0
//End of customize
&.oe_focused
border-color: $tag-border-selected
@include box-shadow(0 1px 2px $tag-border-selected inset)
.oe_searchview_clear
cursor: pointer
position: absolute
top: 0
right: 18px
width: 15px
height: 24px
background: url(../img/search_reset.gif) center center no-repeat
display: none
height: auto
padding-top: 1px
padding-bottom: 1px
.oe_searchview_unfold_drawer
position: absolute
top: 0
right: 0
height: 24px
padding: 0 7px 0 4px
color: #ccc
top: 8px
right: 5px
cursor: pointer
&:hover
color: #999
&:before
position: absolute
top: 10px
right: 7px
width: 0
height: 0
display: inline-block
content: ""
vertical-align: top
border-top: 5px solid #4C4C4C
border-left: 5px solid transparent
border-right: 5px solid transparent
@include opacity()
.oe_searchview_search
@include text-to-icon("r", #a3a3a3)
@include box-shadow(none)
@include radius(0)
cursor: pointer
position: absolute
left: 3px
top: 1px
padding: 0
border: none
background: transparent
top: 8px
left: 5px
.oe_searchview_facets
min-height: 22px
margin: 0 35px 0 15px
margin: 2px 20px 0 0
*
vertical-align: top
display: inline-block
line-height: 17px
font-size: 100%
.oe_searchview_facet
margin: 1px 0
font-size: 11px
&:focus
outline: none
.label
@include radius(0px)
.oe_searchview_input
padding: 0 0 0 6px
padding: 2px 0 0 6px
font-size: 12px
height: 16px
margin-top: 3px
-webkit-user-select: initial
-moz-user-select: initial
user-select: initial
&:focus
outline: none
.oe_searchview_input:first-child
margin-left: 10px
.oe_searchview_facet
position: relative
cursor: pointer
@@ -1209,7 +1072,6 @@ $sheet-padding: 16px
@include box-shadow(0 0 3px 1px $tag-border-selected)
.oe_facet_values
background: $tag-bg-light
@include radius(0 3px 3px 0)
.oe_facet_category, .oe_facet_value
padding: 0 4px
.oe_facet_category
@@ -1218,11 +1080,12 @@ $sheet-padding: 16px
.oe_facet_category.oe_i
font-size: 16px
.oe_facet_value
border-left: 1px solid $tag-border
text-shadow: 0 1px 1px white
border-left: 1px solid #afafb6
color: #4C4C4C
&:last-child
padding-right: 16px
&:first-child
border-left: none
.oe_facet_remove
position: absolute
top: 3px
@@ -1245,14 +1108,13 @@ $sheet-padding: 16px
.oe-autocomplete
display: none
position: absolute
width: 300px
background-color: white
width: 400px
border: 1px solid #afafb6
z-index: 666
margin-top: 2px
cursor: default
@include radius(3px)
@include box-shadow(0 1px 4px rgba(0, 0, 0, 0.3))
ul
list-style-type: none
padding-left: 0
@@ -1276,180 +1138,102 @@ $sheet-padding: 16px
li.oe-separator:last-child
display: none
.oe_searchview_drawer_container
overflow: auto
.oe_searchview_drawer
display: none
width: 100%
cursor: default
display: none
overflow: hidden
border-bottom: 1px solid $tag-border
text-align: left
padding: 8px 0
.badge
font-size: 12px
line-height: 12px
> div:first-child
border: none
padding-left: 0
li:hover:not(.badge)
background-color: $hover-background
.col-md-5
padding-left: 0
dl
margin-bottom: 0
dt
color: $section-title-color
font-size: 13px
line-height: 24px
dd
line-height: 24px
font-size: 13px
padding-top: 3px
h4, h4 *
margin: 0 0 0 2px
padding-left: 20px
cursor: pointer
font-weight: normal
display: inline-block
&:hover
background-color: $hover-background
h4:before
content: "▸ "
color: #a3a3a3
button
margin: 4px 0
.button
border: none
background: transparent
padding: 0 2px
@include box-shadow(none)
@include radius(0)
.oe_searchview_section
ul
margin: 0 8px
padding: 0
list-style: none
display: inline
li
display: inline-block
cursor: pointer
position: relative
margin-right: 8px
> span
display: inline-block
max-width: 250px
text-overflow: ellipsis
vertical-align: bottom
overflow: hidden
form
margin-left: 12px
p
margin: 4px 0
line-height: 18px
button
margin: 0 0 8px -3px // Managed margin-left according bootstrap3
.oe_searchview_savefilter
form
display: none
.oe_searchview_custom
.oe-search-options
a
padding-left: 25px
padding-right: 25px
width: 100%
-webkit-user-select: none
-moz-user-select: none
user-select: none
.oe-apply-filter
margin-left: 25px
.oe-add-filter-menu
display: none
li
cursor: pointer
position: relative
line-height: 14px
margin-right: 0
button
position: absolute
top: 0
right: 5px
a
margin-left: 10px
position: inherit
visibility: hidden
display: inline-block
span:hover:not(.badge)
background-color: $hover-background
li:hover a
visibility: visible
//Customize for searchview label
label
font-weight: normal
//End of Customize
.oe_searchview_dashboard
form
display: none
margin-top: 2px
.oe-add-condition
width: auto
margin-left: 10px
padding-left: 0
padding-right: 10px
.oe-group-selector
margin: 3px 25px
min-width: calc(100% - 40px)
max-width: 250px
width: auto
.divider
display: none
.filters-menu
.divider
display: list-item
.closed-menu
a:before
font-family: FontAwesome
position: absolute
left: 12px
top: 3px
content: "\f0da"
.open-menu
a:before
font-family: FontAwesome
position: absolute
left: 9px
top: 3px
content: "\f0d7"
.oe_searchview_advanced
overflow: auto
form
display: none
margin-top: 8px
button.oe_add_condition:before
content: "Z"
font-family: "entypoRegular" !important
font-size: 24px
font-weight: 300 !important
margin-right: 4px
ul
list-style: none
padding: 0
li
position: relative
list-style: none
margin: 0
.oe-select-group
margin: 3px 25px
.oe-add-group
display: none
.oe-save-name
display: none
margin: 3px 25px
span
white-space: nowrap
&:first-child .searchview_extended_prop_or
visibility: hidden
margin-left: -14px
.searchview_extended_prop_or
opacity: 0.5
margin-left: -14px //Customize 'or' in searchview
.oe_opened
h4:before
content: "▾ "
position: relative
top: -1px
form
display: block
// delete buttons
.oe_searchview_custom_delete, .searchview_extended_delete_prop
display: inline-block
width: 12px
height: 12px
line-height: 12px
padding: 1px
color: #8786b7
line-height: 8px
text-align: center
font-weight: bold
text-shadow: 0 1px 1px white
&:hover
text-decoration: none
color: white
background: #8786b7
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4)
@include radius(2px)
.oe_searchview_custom_delete
display: none
input
margin-left: 0
.searchview_extended_prop_field
display: inline
width: calc(100% - 20px)
.searchview_extended_prop_op
margin: 3px 0
width: calc(100% - 20px)
.searchview_extended_delete_prop
float: right
display: inline
.searchview_extended_prop_value
width: calc(100% - 20px)
> select, > input
width: calc(100% - 20px)
.oe_datepicker_main
width: calc(100% - 20px)
.oe_datepicker_master
width: 100%
.fa-calendar
margin-left: -21px
cursor: pointer
span.fa-trash-o
position: absolute
bottom: 1px
right: 4px
.oe_searchview_custom_private, .oe_searchview_custom_public
&:hover
.oe_searchview_custom_delete
display: inline-block
.oe_searchview_custom_public:after
content: ","
font-family: "entypoRegular" !important
font-size: 22px
font-weight: 300 !important
margin: 0 0 0 4px
padding: 0
top: 6px
right: 10px
cursor: pointer
.dropdown-menu
> li
position: relative
white-space: nowrap
li.oe-filter-condition
white-space: normal
padding-left: 25px
padding-right: 10px
margin-bottom: 5px
a
display: inline-block
input.oe-save-name
width: auto
.oe_pager_value
display: inline-block
// }}}
// Views Common {{{
.oe_view_nocontent
@@ -1784,6 +1568,9 @@ $sheet-padding: 16px
width: auto
.oe_datepicker_root
display: inline-block
.datepickerbutton
margin-left: 5px
cursor: pointer
.oe_form_required
input:not([disabled]):not([readonly]), select:not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly])
background-color: #D2D2FF !important
@@ -2360,6 +2147,8 @@ $sheet-padding: 16px
content: "[newline]"
.oe_debug_view
float: left
margin-top: 5px
width: auto
.oe_debug_view_log
font-size: 95%
line-height: 1.2em
@@ -2410,8 +2199,6 @@ $sheet-padding: 16px
background-color: #7c7bad
// Customize for global tags
button, body
line-height: normal
h1,h2
font-weight: bold
h3
@@ -2447,8 +2234,6 @@ $sheet-padding: 16px
@-moz-document url-prefix()
.openerp
.oe_searchview .oe_searchview_search
top: -1px
.oe_form_field_many2one .oe_m2o_cm_button
line-height: 18px
.oe_webclient
+11 -1
View File
@@ -287,7 +287,17 @@ openerp.ParentedMixin = {
});
this.setParent(undefined);
this.__parentedDestroyed = true;
}
},
/**
* Find the closest ancestor matching predicate
*/
findAncestor: function (predicate) {
var ancestor = this;
while (!(predicate(ancestor)) && ancestor && ancestor.getParent) {
ancestor = ancestor.getParent();
}
return ancestor;
},
};
/**
File diff suppressed because it is too large Load Diff
+21 -46
View File
@@ -85,8 +85,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* @param {instance.web.Session} session the current openerp session
* @param {instance.web.DataSet} dataset the dataset this view will work with
* @param {String} view_id the identifier of the OpenERP view object
* @param {Object} options
* - resize_textareas : [true|false|max_height]
*
* @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
*/
@@ -257,7 +255,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.dataset.ids.push(state.id);
}
this.dataset.select_id(state.id);
this.do_show({ reload: warm });
this.do_show();
}
},
/**
@@ -425,7 +423,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
$el.removeAttr("disabled");
});
});
this.do_update_pager();
},
do_update_pager: function(hide_index) {
this.$pager.toggle(this.dataset.ids.length > 1);
@@ -3973,7 +3970,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
disable_utility_classes: true,
init: function(field_manager, node) {
this._super(field_manager, node);
lazy_build_o2m_kanban_view();
this.is_loaded = $.Deferred();
this.initial_is_loaded = this.is_loaded;
this.form_last_update = $.Deferred();
@@ -4143,14 +4139,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
/**
* Returns the current active view if any.
*/
if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
this.viewmanager.views[this.viewmanager.active_view] &&
this.viewmanager.views[this.viewmanager.active_view].controller) {
return {
type: this.viewmanager.active_view,
controller: this.viewmanager.views[this.viewmanager.active_view].controller
};
}
return (this.viewmanager && this.viewmanager.active_view);
},
set_value: function(value_) {
value_ = value_ || [];
@@ -4242,12 +4231,12 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
save_any_view: function() {
var view = this.get_active_view();
if (view) {
if (this.viewmanager.active_view === "form") {
if (this.viewmanager.active_view.type === "form") {
if (view.controller.is_initialized.state() !== 'resolved') {
return $.when(false);
}
return $.when(view.controller.save());
} else if (this.viewmanager.active_view === "list") {
} else if (this.viewmanager.active_view.type === "list") {
return $.when(view.controller.ensure_saved());
}
}
@@ -4258,7 +4247,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
if (!view){
return true;
}
switch (this.viewmanager.active_view) {
switch (this.viewmanager.active_view.type) {
case 'form':
return _(view.controller.fields).chain()
.invoke('is_valid')
@@ -4275,10 +4264,9 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
template: 'One2Many.viewmanager',
init: function(parent, dataset, views, flags) {
this._super(parent, dataset, views, _.extend({}, flags, {$sidebar: false}));
this.registry = this.registry.extend({
this.registry = instance.web.views.extend({
list: 'instance.web.form.One2ManyListView',
form: 'instance.web.form.One2ManyFormView',
kanban: 'instance.web.form.One2ManyKanbanView',
});
this.__ignore_blur = false;
},
@@ -4548,13 +4536,6 @@ instance.web.form.One2ManyFormView = instance.web.FormView.extend({
}
});
var lazy_build_o2m_kanban_view = function() {
if (! instance.web_kanban || instance.web.form.One2ManyKanbanView)
return;
instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({
});
};
instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
template: "FieldMany2ManyTags",
tag_template: "FieldMany2ManyTag",
@@ -5147,8 +5128,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
this.domain = domain || [];
this.context = context || {};
this.options = options;
_.defaults(this.options, {
});
_.defaults(this.options, {});
},
init_dataset: function() {
var self = this;
@@ -5293,22 +5273,20 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
this.display_popup();
},
start: function() {
var self = this;
this.init_dataset();
if (this.options.initial_view == "search") {
instance.web.pyeval.eval_domains_and_contexts({
var context = instance.web.pyeval.sync_eval_domains_and_contexts({
domains: [],
contexts: [this.context]
}).done(function (results) {
var search_defaults = {};
_.each(results.context, function (value_, key) {
var match = /^search_default_(.*)$/.exec(key);
if (match) {
search_defaults[match[1]] = value_;
}
});
self.setup_search_view(search_defaults);
}).context;
var search_defaults = {};
_.each(context, function (value_, key) {
var match = /^search_default_(.*)$/.exec(key);
if (match) {
search_defaults[match[1]] = value_;
}
});
this.setup_search_view(search_defaults);
} else { // "form"
this.new_object();
}
@@ -5318,12 +5296,9 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
if (this.searchview) {
this.searchview.destroy();
}
if (this.searchview_drawer) {
this.searchview_drawer.destroy();
}
var $buttons = this.$('.oe-search-options');
this.searchview = new instance.web.SearchView(this,
this.dataset, false, search_defaults);
this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview);
this.dataset, false, search_defaults, {$buttons: $buttons});
this.searchview.on('search_data', self, function(domains, contexts, groupbys) {
if (self.initial_ids) {
self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
@@ -5333,7 +5308,8 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys);
}
});
this.searchview.on("search_view_loaded", self, function() {
this.searchview.appendTo(this.$(".oe_popup_search")).done(function() {
self.searchview.toggle_visibility(true);
self.view_list = new instance.web.form.SelectCreateListView(self,
self.dataset, false,
_.extend({'deletable': false,
@@ -5368,8 +5344,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
self.new_object();
});
});
});
this.searchview.appendTo(this.$(".oe_popup_search"));
});
},
do_search: function(domains, contexts, groupbys) {
var self = this;
+5 -10
View File
@@ -289,7 +289,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
// Pager
if (!this.$pager) {
this.$pager = $(QWeb.render("ListView.pager", {'widget':self}));
this.$pager = $(QWeb.render("ListView.pager", {'widget':self})).hide();
if (this.options.$buttons) {
this.$pager.appendTo(this.options.$pager);
} else {
@@ -404,11 +404,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
var total = dataset.size();
var limit = this.limit() || total;
if (total === 0)
this.$pager.hide();
else
this.$pager.css("display", "");
this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
this.$pager.find('.oe-pager-button').toggle(total > limit);
this.$pager.find('.oe_pager_value').toggle(total !== 0);
var spager = '-';
if (total) {
var range_start = this.page * limit + 1;
@@ -1517,12 +1514,10 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
}))
.end()
.find('button[data-pager-action=previous]')
.css('visibility',
page === 0 ? 'hidden' : '')
.toggleClass('disabled', page === 0)
.end()
.find('button[data-pager-action=next]')
.css('visibility',
page === pages - 1 ? 'hidden' : '');
.toggleClass('disabled', page === pages - 1);
}
}
+89
View File
@@ -0,0 +1,89 @@
/*---------------------------------------------------------
* Odoo Pivot Table view
*---------------------------------------------------------*/
(function () {
'use strict';
var instance = openerp,
_lt = instance.web._lt,
_t = instance.web._t,
QWeb = instance.web.qweb;
instance.web.views.add('pivot', 'instance.web.PivotView');
instance.web.PivotView = instance.web.View.extend({
template: 'PivotView',
display_name: _lt('Pivot'),
view_type: 'pivot',
init: function(parent, dataset, view_id, options) {
this._super(parent, dataset, view_id, options);
this.model = new instance.web.Model(dataset.model, {group_by_no_leaf: true});
this.action_manager = parent.action_manager;
this.$buttons = options.$buttons;
this.fields = {};
this.measures = {};
this.groupable_fields = {};
this.row_groupbys = [];
this.col_groupbys = [];
this.active_measures = [];
},
start: function () {
var self = this,
load_fields = this.model.call('fields_get', [])
.then(this.prepare_fields.bind(this));
return $.when(this._super(), load_fields).then(function () {
var context = {measures: _.pairs(self.measures)};
self.$buttons.html(QWeb.render('PivotView.buttons', self), context);
});
},
view_loading: function (fvg) {
var self = this;
this.do_push_state({});
fvg.arch.children.forEach(function (field) {
var name = field.attrs.name + (field.attrs.interval || '');
//noinspection FallThroughInSwitchStatementJS
switch (field.attrs.type) {
case 'measure':
self.active_measures.push(name);
break;
case 'col':
self.col_groupbys.push(name);
break;
default:
if ('operator' in field.attrs) {
self.active_measures.push(name);
break;
}
case 'row':
self.row_groupbys.push(name);
}
});
},
prepare_fields: function (fields) {
var self = this,
groupable_types = ['many2one', 'char', 'boolean',
'selection', 'date', 'datetime'];
this.fields = fields;
_.each(fields, function (field, name) {
if ((name !== 'id') && (field.store === true)) {
if (field.type === 'integer' || field.type === 'float') {
self.measures[name] = field;
}
if (_.contains(groupable_types, field.type)) {
self.groupable_fields[name] = field;
}
}
});
},
do_search: function (domain, context, group_by) {
},
});
})();
File diff suppressed because it is too large Load Diff
+253 -324
View File
@@ -25,13 +25,6 @@
</div>
</div>
</t>
<t t-name="Tipsy.alert">
<a class="oe_tooltip_close oe_e">[</a>
<span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert ui-state-error"></span>
<div class="oe_tooltip_message">
<t t-esc="message"/>
</div>
</t>
<t t-name="Dialog">
<div class="modal" tabindex="-1" data-backdrop="static" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg">
@@ -476,82 +469,76 @@
</div>
</t>
<t t-name="ViewManager">
<div class="oe_view_manager">
<table class="oe_view_manager_header">
<col width="20%"/>
<col width="35%"/>
<col width="15%"/>
<col width="30%"/>
<tr class="oe_header_row oe_header_row_top">
<td colspan="2">
<h2 class="oe_view_title" t-if="widget.flags.display_title !== false">
<span class="oe_view_title_text oe_breadcrumb_title"/>
</h2>
</td>
<td colspan="2">
<div class="oe_view_manager_view_search"/>
</td>
</tr>
<tr class="oe_header_row">
<td>
<div class="oe_view_manager_buttons"/>
</td>
<td colspan="2">
<div class="oe_view_manager_sidebar"/>
</td>
<td>
<ul class="oe_view_manager_switch oe_button_group oe_right">
<t t-if="widget.views_src.length > 1" t-foreach="widget.views_src" t-as="view">
<li class="oe_e">
<a t-attf-class="oe_vm_switch_#{view.view_type}" t-att-data-view-type="view.view_type"
t-att-title="view.button_label"/>
</li>
</t>
</ul>
<div class="oe_view_manager_pager oe_right"/>
</td>
</tr>
</table>
<div class="oe_view_manager_wrapper">
<div>
<div class="oe_view_manager_body">
<div class="oe_searchview_drawer_container"/>
<t t-foreach="widget.views_src" t-as="view">
<div t-attf-class="oe_view_manager_view_#{view.view_type}"/>
<div t-name="ViewManager" class="oe-view-manager">
<div class="oe-view-manager-header container-fluid">
<div class="row">
<div class="col-md-6 oe-header-title">
<div t-if="widget.session.debug" class="oe_debug_view btn-group btn-group-sm"/>
<ol class="oe-view-title breadcrumb" t-if="widget.flags.display_title !== false">
</ol>
</div>
<div class="oe-view-manager-search-view col-md-6" />
</div>
<div class="row">
<div class="col-md-6">
<div class="oe-view-manager-buttons">
<t t-foreach="widget.views" t-as="view">
<div t-attf-class="oe-#{view}-buttons"/>
</t>
</div>
<div class="oe-view-manager-sidebar"></div>
</div>
<div class="col-md-6">
<div class="oe-search-options btn-group"/>
<div class="oe-right-toolbar">
<div class="oe-view-manager-pager"></div>
<t t-if="widget.multiple_views">
<div class="oe-view-manager-switch btn-group btn-group-sm">
<t t-foreach="widget.view_order" t-as="view">
<button type="button" t-attf-class="btn btn-default fa oe-vm-switch-#{view.type}" t-att-data-view-type="view.type" t-att-title="view.label" t-if="view.type!=='form'">
</button>
</t>
</div>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-name="ViewManagerAction" t-extend="ViewManager">
<t t-jquery="h2.oe_view_title" t-operation="before">
<select t-if="widget.session.debug" class="oe_debug_view"/>
</t>
</t>
<div class="oe-view-manager-content">
<div class="oe-view-manager-body">
<div class="oe-view-manager-views">
<t t-foreach="widget.views" t-as="view">
<div t-attf-class="oe-view-manager-view-#{view}"/>
</t>
</div>
</div>
</div>
</div>
<t t-name="ViewManagerDebug">
<option value="">Debug View#<t t-esc="view.fields_view.view_id"/></option>
<t t-if="view_manager.active_view === 'form'">
<option value="get_metadata">View Metadata</option>
<option value="toggle_layout_outline">Toggle Form Layout Outline</option>
<option value="set_defaults">Set Defaults</option>
</t>
<option value="tests">JS Tests</option>
<option value="fields">View Fields</option>
<option value="fvg">Fields View Get</option>
<option value="manage_filters">Manage Filters</option>
<t t-if="view_manager.session.uid === 1">
<option value="translate">Technical translation</option>
<option value="manage_views">Manage Views</option>
<option value="edit" data-model="ir.ui.view" t-att-data-id="view.fields_view.view_id">Edit <t t-esc="_.str.capitalize(view.fields_view.type)"/>View</option>
<option t-if="view_manager.searchview" value="edit" data-model="ir.ui.view" t-att-data-id="view_manager.searchview.view_id">Edit SearchView</option>
<option t-if="view_manager.action" value="edit" t-att-data-model="view_manager.action.type" t-att-data-id="view_manager.action.id">Edit Action</option>
<option value="edit_workflow">Edit Workflow</option>
<option value="print_workflow">Print Workflow</option>
</t>
<button type="button" class="btn btn-default dropdown-toggle fa fa-bug oe-view-manager-debug" data-toggle="dropdown"> <span class="caret"/>
</button>
<ul class="dropdown-menu" role="menu">
<t t-if="view_manager.active_view.type === 'form'">
<li><a data-action="get_metadata">View Metadata</a></li>
<li><a data-action="toggle_layout_outline">Toggle Form Layout Outline</a></li>
<li><a data-action="set_defaults">Set Defaults</a></li>
</t>
<li><a data-action="tests">JS Tests</a></li>
<li><a data-action="fields">View Fields</a></li>
<li><a data-action="fvg">Fields View Get</a></li>
<li><a data-action="manage_filters">Manage Filters</a></li>
<t t-if="view_manager.session.uid === 1">
<li><a data-action="translate">Technical Translation</a></li>
<li><a data-action="manage_views">Manage Views</a></li>
<li><a data-action="edit" data-model="ir.ui.view" t-att-data-id="view.fields_view.view_id">Edit <t t-esc="_.str.capitalize(view.fields_view.type)"/> View</a></li>
<li t-if="view_manager.searchview"><a data-action="edit" data-model="ir.ui.view" t-att-data-id="view_manager.searchview.view_id">Edit SearchView</a></li>
<li t-if="view_manager.action"><a data-action="edit" t-att-data-model="view_manager.action.type" t-att-data-id="view_manager.action.id">Edit Action</a></li>
<li><a data-action="edit_workflow">Edit Workflow</a></li>
<li><a data-action="print_workflow">Print Workflow</a></li>
<li class="divider"/>
<li><a data-action="leave_debug">Leave Debug Mode</a></li>
</t>
</ul>
</t>
<t t-name="ViewManagerDebugViewLog">
<div class="oe_debug_view_log">
@@ -583,83 +570,69 @@
</table>
</div>
</t>
<t t-name="ViewPager">
<div t-name="ViewPager">
<div class="oe_pager_value">
<t t-raw="0"/>
</div>
<ul class="oe_pager_group">
<!--
<button class="oe_button oe_button_pager" type="button" data-pager-action="first">
<img t-att-src='_s + "/web/static/src/img/pager_first.png"'/>
</button>
-->
<li>
<a class="oe_i" type="button" data-pager-action="previous">(</a>
</li>
<li>
<a class="oe_i" type="button" data-pager-action="next">)</a>
</li>
<!--
<button class="oe_button oe_button_pager" type="button" data-pager-action="last">
<img t-att-src='_s + "/web/static/src/img/pager_last.png"'/>
</button>
-->
</ul>
</t>
<t t-name="Sidebar">
<div class="oe_sidebar">
<t t-foreach="widget.sections" t-as="section">
<div class="oe_form_dropdown_section">
<button class="oe_dropdown_toggle oe_dropdown_arrow" t-if="section.name != 'buttons'">
<t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/>
<t t-esc="section.label"/>
<i class="fa fa-caret-down"/>
</button>
<t t-if="section.name == 'buttons'" t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<button t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url"
target="_blank" class="oe_sidebar_button oe_highlight">
<t t-raw="item.label"/>
</button>
</t>
<ul class="oe_dropdown_menu">
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<t t-if="section.name == 'files'">
<t t-set="item.title">
<b>Attachment : </b><br/>
<t t-raw="item.name"/>
</t>
<t t-if="item.create_uid and item.create_uid[0]" t-set="item.title">
<t t-raw="item.title"/><br/>
<b>Created by : </b><br/>
<t t-raw="item.create_uid[1] + ' ' + item.create_date"/>
</t>
<t t-if="item.create_uid and item.write_uid and item.create_uid[0] != item.write_uid[0]" t-set="item.title">
<t t-raw="item.title"/><br/>
<b>Modified by : </b><br/>
<t t-raw="item.write_uid[1] + ' ' + item.write_date"/>
</t>
</t>
<a class="oe_sidebar_action_a" t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank">
<t t-raw="item.label"/>
</a>
<a t-if="section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
</li>
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<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"/>
<span>Add...</span>
</t>
</li>
</ul>
</div>
</t>
<div class="btn-group btn-group-sm oe-pager-buttons">
<a class="fa fa-chevron-left btn btn-default oe-pager-button" type="button" data-pager-action="previous"></a>
<a class="fa fa-chevron-right btn btn-default oe-pager-button" type="button" data-pager-action="next"></a>
</div>
</t>
</div>
<div t-name="Sidebar" class="oe_sidebar btn-group">
<t t-foreach="widget.sections" t-as="section">
<div class="oe_form_dropdown_section btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle"
t-if="section.name != 'buttons'"
data-toggle="dropdown">
<t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/>
<t t-esc="section.label"/>
<span class="caret"></span>
</button>
<t t-if="section.name == 'buttons'" t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<button t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url"
target="_blank" class="oe_sidebar_button oe_highlight">
<t t-raw="item.label"/>
</button>
</t>
<ul class="dropdown-menu" role="menu">
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<t t-if="section.name == 'files'">
<t t-set="item.title">
<b>Attachment : </b><br/>
<t t-raw="item.name"/>
</t>
<t t-if="item.create_uid and item.create_uid[0]" t-set="item.title">
<t t-raw="item.title"/><br/>
<b>Created by : </b><br/>
<t t-raw="item.create_uid[1] + ' ' + item.create_date"/>
</t>
<t t-if="item.create_uid and item.write_uid and item.create_uid[0] != item.write_uid[0]" t-set="item.title">
<t t-raw="item.title"/><br/>
<b>Modified by : </b><br/>
<t t-raw="item.write_uid[1] + ' ' + item.write_date"/>
</t>
</t>
<a class="oe_file_attachment" t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url">
<t t-raw="item.label"/>
</a>
<a t-if="section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
</li>
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<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"/>
<span>Add...</span>
</t>
</li>
</ul>
</div>
</t>
</div>
<t t-name="TreeView">
<select t-if="toolbar" style="width: 30%">
@@ -745,19 +718,14 @@
<t t-name="ListView.buttons">
<div class="oe_list_buttons">
<t t-if="!widget.no_leaf and widget.options.action_buttons !== false and widget.options.addable and widget.is_action_enabled('create')">
<button type="button" class="oe_button oe_list_add oe_highlight">
<button type="button" class="oe_list_add btn btn-primary btn-sm">
<t t-esc="widget.options.addable"/>
</button>
</t>
</div>
</t>
<t t-name="ListView.pager">
<div class="oe_list_pager" t-att-colspan="widget.columns_count">
<t t-if="!widget.no_leaf and widget.options.pager !== false" t-call="ViewPager">
<span class="oe_list_pager_state">
</span>
</t>
</div>
<t t-name="ListView.pager" t-if="!widget.no_leaf and widget.options.pager !== false" t-call="ViewPager">
<span class="oe_list_pager_state"></span>
</t>
<t t-name="ListView.rows" t-foreach="records.length" t-as="index">
<t t-call="ListView.row">
@@ -825,29 +793,29 @@
<div class="oe_form_container"/>
</div>
</t>
<div t-name="FormView.buttons" class="oe_form_buttons">
<div t-name="FormView.buttons">
<t t-if="widget.options.action_buttons !== false">
<span class="oe_form_buttons_view">
<!-- required for the bounce effect on button -->
<div t-if="widget.is_action_enabled('edit')" style="display: inline-block;">
<button type="button" class="oe_button oe_form_button_edit" accesskey="E">Edit</button>
</div>
<div class="btn-group btn-group-sm oe_form_buttons_view">
<button t-if="widget.is_action_enabled('edit')"
type="button"
class="oe_form_button_edit btn btn-default" accesskey="E">
Edit
</button>
<button t-if="widget.is_action_enabled('create')"
type="button" class="oe_button oe_form_button_create" accesskey="C">Create</button>
</span>
type="button" class="oe_form_button_create btn btn-default"
accesskey="C">
Create
</button>
</div>
<span class="oe_form_buttons_edit">
<button type="button" class="oe_button oe_form_button_save oe_highlight" accesskey="S">Save</button>
<button type="button" class="oe_form_button_save btn btn-primary btn-sm" accesskey="S">Save</button>
<span class="oe_fade">or</span>
<a href="#" class="oe_bold oe_form_button_cancel" accesskey="D">Discard</a>
</span>
</t>
</div>
<t t-name="FormView.pager">
<div class="oe_form_pager">
<t t-if="widget.options.pager !== false" t-call="ViewPager">
<span class="oe_form_pager_state"></span>
</t>
</div>
<t t-name="FormView.pager" t-if="widget.options.pager !== false" t-call="ViewPager">
<span class="oe_form_pager_state"></span>
</t>
<form t-name="FormView.set_default">
<t t-set="args" t-value="widget.dialog_options.args"/>
@@ -947,6 +915,37 @@
</label>
</t>
<div t-name="PivotView" class="oe_pivot"/>
<t t-name="PivotView.buttons">
<div class="btn-group btn-group-sm">
<button class="btn btn-default fa fa-expand oe-pivot-flip"></button>
<button class="btn btn-default fa fa-arrows-alt oe-pivot-expand-all"></button>
<button class="btn btn-default fa fa-download oe-pivot-download"></button>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
Measures <span class="caret"/>
</button>
<ul class="oe-measure-list dropdown-menu">
<li t-foreach="measures" t-as="measure" t-att-data-field="measure">
<a><t t-esc="measures[measure].string"/></a>
</li>
</ul>
</div>
</t>
<div t-name="PivotView.nodata" class="oe_view_nocontent" >
<div class="oe_empty_custom_dashboard">
<p><b>No data to display.</b></p>
<p>
No data available for this pivot table. Try to add some records, or make sure
that there is at least one measure and no active filter in the search bar.
</p>
</div>
</div>
<t t-name="Widget">
Unhandled widget
<t t-js="dict">console.warn('Unhandled widget', dict.widget);</t>
@@ -1129,9 +1128,7 @@
t-att-name="widget.name"
t-att-placeholder="placeholder"
class="oe_datepicker_master"
/><span><img class="oe_input_icon oe_datepicker_trigger datepickerbutton" draggable="false"
t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
title="Select date" width="16" height="16" border="0"/></span>
/><span class="fa fa-calendar datepickerbutton"/>
</div>
</span>
</t>
@@ -1489,11 +1486,8 @@
<table style="width:100%">
<tr style="width:100%">
<td style="width:100%">
<div class="oe_popup_search" style="width:100%"></div>
</td>
</tr>
<tr style="width:100%">
<td style="width:100%">
<div class="oe-search-options btn-group"/>
<div class="oe_popup_search"></div>
<div class="oe_popup_list_pager"></div>
</td>
</tr>
@@ -1537,7 +1531,7 @@
<t t-name="One2Many.viewmanager" t-extend="ViewManager">
<t t-jquery=".oe-view-manager-header">
this.attr('t-if', 'views.length != 1');
this.attr('t-if', 'widget.view_order.length != 1');
</t>
</t>
<t t-name="One2Many.formview" t-extend="FormView">
@@ -1558,17 +1552,10 @@
</t>
</t>
<div t-name="SearchView" class="oe_searchview">
<div t-name="SearchView" class="oe_searchview form-control input-sm">
<div class="oe_searchview_search fa fa-lg fa-search" title="Search Again"/>
<div class="oe_searchview_facets"/>
<div class="oe_searchview_clear"/>
<div class="oe_searchview_unfold_drawer" title="Advanced Search..."/>
<button type="button" class="oe_searchview_search"
title="Search Again">Search</button>
</div>
<div t-name="SearchViewDrawer" class="oe_searchview_drawer" style="display:none;">
<div class="col-md-7"></div>
<div class="col-md-5"></div>
<div class="oe_searchview_unfold_drawer fa fa-lg fa-fw fa-chevron-right" title="Advanced Search..."/>
</div>
<div t-name="SearchView.InputView"
@@ -1577,86 +1564,28 @@
tabindex="0"/>
<!-- tabindex: makes div focusable -->
<div t-name="SearchView.FacetView"
class="oe_tag oe_tag_dark oe_searchview_facet"
tabindex="0"
><span class="oe_facet_remove">x</span
><span class="oe_facet_category oe_i" t-if="widget.model.has('icon')">
<t t-esc="widget.model.get('icon')"/>
</span
><span class="oe_facet_category" t-if="!widget.model.has('icon')">
class="oe_tag oe_searchview_facet"
tabindex="0">
<span class="oe_facet_remove">x</span>
<span t-if="widget.model.has('icon')"
t-att-class="'label label-default fa ' + widget.model.get('icon')">
</span>
<span class="label label-default" t-if="!widget.model.has('icon')">
<t t-esc="widget.model.get('category')"/>
</span ><span class="oe_facet_values"
/></div>
</span >
<span class="oe_facet_values"/>
</div>
<span t-name="SearchView.FacetView.Value" class="oe_facet_value">
<t t-esc="widget.model.get('label')"/>
</span>
<t t-name="SearchView.managed-filters">
<option class="oe_search_filters_title" value="">Filters</option>
<optgroup label="-- Filters --">
<t t-foreach="filters" t-as="filter">
<option t-attf-value="get:#{filter_index}"
t-att-disabled="filter.disabled and 'disabled'"
t-att-title="filter.disabled and disabled_filter_message">
<t t-esc="filter.name"/>
</option>
</t>
</optgroup>
<optgroup label="-- Actions --">
<option value="advanced_filter">Add Advanced Filter</option>
<option value="save_filter">Save Filter</option>
<option value="manage_filters">Manage Filters</option>
</optgroup>
</t>
<t t-name="SearchView.managed-filters.add">
<div>
<p>Filter Name:</p>
<input type="text"/>
<p>(Any existing filter with the same name will be replaced)</p>
</div>
</t>
<t t-name="SearchView.render_lines">
<table class="oe_search_render_line" border="0" cellspacing="0" cellpadding="0"
t-foreach="lines" t-as="line">
<tr>
<td t-foreach="line" t-as="widget" class="oe_searchview_field">
<t t-raw="widget.render(defaults)"/>
</td>
</tr>
</table>
</t>
<button t-name="SearchView.filter" type="button"
t-att-id="element_id"
t-att-title="attrs.help"
t-att-class="classes.join(' ')"
t-att-style="style"
t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined">
<img t-att-src="_s + '/web/static/src/img/icons/' + (attrs.icon || 'gtk-home') + '.png'" width="16" height="16"/>
<br t-if="attrs.string"/>
<t t-esc="attrs.string"/>
</button>
<ul t-name="SearchView.filters">
<t t-name="SearchView.filters">
<li t-foreach="widget.filters" t-as="filter" t-if="!filter.visible || filter.visible()"
t-att-title="filter.attrs.string ? filter.attrs.help : undefined"
t-att-data-index="filter_index">
<t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>
<a>
<t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>
</a>
</li>
</ul>
<t t-name="SearchView.filters.facet">
<div class="category oe_filter_category"><t t-esc="facet.get('category')"/></div>
<t t-set="val" t-value="facet.get('json')"/>
<div t-if="!(val instanceof Array)" class="search_facet_input_container">
<t t-esc="facet.get('value')"/>
</div>
<t t-if="val instanceof Array">
<div class="search_facet_input_container"
t-foreach="facet.get('json')" t-as="filter">
<t t-esc="filter.attrs.string || filter.attrs.name"/>
</div>
</t>
<div class="search_facet_remove VS-icon VS-icon-cancel"/>
</t>
<t t-name="SearchView.field">
<label t-att-class="'oe_label' + (attrs.help ? '_help' : '')"
@@ -1714,82 +1643,82 @@
<t t-if="filters.length" t-raw="filters.render(defaults)"/>
</div>
</t>
<t t-name="SearchView.util.expand">
<div t-att-class="'searchview_group ' + (expand == '0' ? 'folded' : 'expanded')">
<a t-if="label" class="searchview_group_string" href="#">
<t t-esc="label"/>
</a>
<div class="searchview_group_content">
<t t-raw="content"/>
</div>
</div>
<div t-name="SearchView.FilterMenu" class="btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="fa fa-filter"/> Filters <span class="caret"/>
</button>
<ul class="dropdown-menu filters-menu" role="menu">
<li class="oe-add-filter closed-menu"><a>Add Custom Filter</a></li>
<li class="oe-add-filter-menu"><button class="btn btn-default oe-apply-filter">Apply</button><a class="oe-add-condition"><span class="fa fa-plus-circle"/> Add a condition</a></li>
</ul>
</div>
<div t-name="SearchView.GroupByMenu" class="btn-group btn-group-sm oe-groupby-menu">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="fa fa-bars"/> Group By <span class="caret"/>
</button>
<ul class="dropdown-menu group-by-menu" role="menu">
<li class="divider"/>
<li class="add-custom-group closed-menu"><a>Add custom group</a></li>
</ul>
</div>
<t t-name="GroupByMenuSelector">
<li><select class="form-control oe-add-group oe-group-selector">
<t t-foreach="groupable_fields" t-as="field">
<option t-att-data-name="field"><t t-esc="groupable_fields[field].string"/></option>
</t>
</select></li>
<li><button class="btn btn-default oe-add-group oe-select-group">Apply</button></li>
</t>
<div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section">
</div>
<div t-name="SearchView.Custom" class="oe_searchview_custom oe_searchview_section">
<dl class="dl-horizontal">
<dt><span class="oe_i">M</span> Favorites</dt>
<dd><ul class="oe_searchview_custom_list"/></dd>
</dl>
</div>
<div t-name="SearchView.SaveFilter" class="oe_searchview_savefilter">
<h4>Save current filter</h4>
<form class="oe_form">
<p class="oe_form_required"><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
<p>
<input id="oe_searchview_custom_public" type="checkbox"/>
<label for="oe_searchview_custom_public">Share with all users</label>
<input id="oe_searchview_custom_default" type="checkbox"/>
<label for="oe_searchview_custom_default">Use by default</label>
</p>
<button>Save</button>
</form>
</div>
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
<h4>Advanced Search</h4>
<form>
<ul>
</ul>
<button class="oe_add_condition button" type="button">Add a condition</button><br/>
<button class="oe_apply" type="submit">Apply</button>
</form>
<div t-name="SearchView.FavoriteMenu" class="btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="fa fa-star"/> Favorites <span class="caret"/>
</button>
<ul class="dropdown-menu favorites-menu" role="menu">
<li class="divider"/>
<li class="oe-save-search closed-menu"><a>Save current search</a></li>
<li class="oe-save-name"><input type="text" class="form-control"></input></li>
<li class="oe-save-name">
<span><input type="checkbox"/>Use by default</span>
</li>
<li class="oe-save-name">
<span><input type="checkbox"/>Share with all users</span>
</li>
<li class="oe-save-name"><button class="btn btn-default">Save</button></li>
</ul>
</div>
<t t-name="SearchView.extended_search.proposition">
<li>
<span class="searchview_extended_prop_or">or</span>
<select class="searchview_extended_prop_field">
<t t-foreach="widget.attrs.fields" t-as="field">
<option t-att="{'selected': field === widget.attrs.selected ? 'selected' : null}"
t-att-value="field.name">
<t t-esc="field.string"/>
</option>
</t>
</select>
<select class="searchview_extended_prop_op"/>
<li class="oe-filter-condition">
<span>
<select class="searchview_extended_prop_field form-control">
<t t-foreach="widget.attrs.fields" t-as="field">
<option t-att="{'selected': field === widget.attrs.selected ? 'selected' : null}"
t-att-value="field.name">
<t t-esc="field.string"/>
</option>
</t>
</select>
<span class="searchview_extended_delete_prop fa fa-trash-o"/>
</span>
<select class="searchview_extended_prop_op form-control"/>
<span class="searchview_extended_prop_value"/>
<a class="searchview_extended_delete_prop button">x</a>
</li>
</t>
<t t-name="SearchView.extended_search.proposition.char">
<input class="field_char"/>
<input class="field_char form-control"/>
</t>
<t t-name="SearchView.extended_search.proposition.empty">
<span/>
</t>
<t t-name="SearchView.extended_search.proposition.integer">
<input type="number" class="field_integer" value = "0" step="1"/>
<input type="number" class="field_integer form-control" value = "0" step="1"/>
</t>
<t t-name="SearchView.extended_search.proposition.float">
<input type="number" class="field_float" value = "0.0" step="0.01"/>
<input type="number" class="field_float form-control" value = "0.0" step="0.01"/>
</t>
<t t-name="SearchView.extended_search.proposition.selection">
<select>
<select class="form-control">
<t t-foreach="widget.field.selection" t-as="element">
<option t-att-value="element[0]"><t t-esc="element[1]"/></option>
</t>
+78 -274
View File
@@ -146,7 +146,7 @@ openerp.testing.section('search.query', {
* @param [defaults={}]
* @return {instance.web.SearchView}
*/
var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
var makeSearchView = function (instance, dummy_widget_attributes, defaults, options) {
instance.web.search.fields.add(
'dummy', 'instance.dummy.DummyWidget');
instance.dummy = {};
@@ -175,8 +175,8 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
var dataset = new instance.web.DataSet(null, 'dummy.model');
var mock_parent = {getParent: function () {return null;}};
var view = new instance.web.SearchView(mock_parent, dataset, false, defaults);
options = _.defaults(options || {}, {$buttons: $('<div>')});
var view = new instance.web.SearchView(mock_parent, dataset, false, defaults, options);
var self = this;
view.on('invalid_search', self, function () {
ok(false, JSON.stringify([].slice(arguments)));
@@ -1018,42 +1018,21 @@ openerp.testing.section('search.serialization', {
return $.when(t1, t2);
});
});
openerp.testing.section('search.removal', {
openerp.testing.section('search.menus', {
dependencies: ['web.search'],
rpc: 'mock',
templates: true
}, function (test) {
test('clear button', {asserts: 2}, function (instance, $fix) {
var view = makeSearchView(instance, {
facet_for_defaults: function (defaults) {
return $.when({
field: this,
category: 'Dummy',
values: [{label: 'dummy', value: defaults.dummy}]
});
}
}, {dummy: 42});
test('is-drawn', {asserts: 3}, function (instance, $fix) {
var view = makeSearchView(instance, false, false, {$buttons: $('<div>')});
return view.appendTo($fix)
.done(function () {
equal(view.query.length, 1, "view should have default facet");
$fix.find('.oe_searchview_clear').click();
equal(view.query.length, 0, "cleared view should not have any facet");
});
});
});
openerp.testing.section('search.drawer', {
dependencies: ['web.search'],
rpc: 'mock',
templates: true
}, function (test) {
test('is-drawn', {asserts: 2}, function (instance, $fix) {
var view = makeSearchView(instance);
return view.appendTo($fix)
.done(function () {
ok($fix.find('.oe_searchview_filters').length,
"filters drawer control has been drawn");
ok($fix.find('.oe_searchview_advanced').length,
"filters advanced search has been drawn");
ok(view.$buttons.find('.filters-menu').length,
"filters menu has been drawn");
ok(view.$buttons.find('.group-by-menu').length,
"group by menu has been drawn");
ok(view.$buttons.find('.favorites-menu').length,
"favorites menu has been drawn");
});
});
});
@@ -1076,18 +1055,25 @@ openerp.testing.section('search.filters', {
});
}
}, function (test) {
test('drawn', {asserts: 3}, function (instance, $fix) {
test('drawn', {asserts: 4}, function (instance, $fix) {
var view = makeSearchView(instance);
return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
// 3 filters, 1 filtergroup, 1 custom report widget,
// 1 Filters, 1 SaveFilter widget, and 1 advanced
equal(view.drawer.inputs.length, 8,
'view should have 8 inputs total');
equal($fs.children().length, 3,
"drawer should have a filter group with 3 filters");
equal(_.str.strip($fs.children().eq(0).text()), "Foo1",
var $filters = view.$buttons.find('.filters-menu li'),
$favorites = view.$buttons.find('.favorites-menu li'),
$groupby = view.$buttons.find('.group-by-menu li');
// 3 filters, 1 separator, 1 button add filter,
// 1 filter condition menu, 1 apply button
equal($filters.length, 7,
'filter menu should have 7 elements total');
// 1 divider, 1 save search button, 1 text input, 2 checkboxes,
// 1 save button
equal($favorites.length, 6,
"favorites menu should have 6 elements");
// 1 divider, 1 add custom group button, 1 select groupby, 1 apply button,
equal($groupby.length, 4,
"groupby menu should have 4 element");
equal(_.str.strip($filters.find('a')[0].textContent), "Foo1",
"Text content of first filter option should match filter string");
});
});
@@ -1095,8 +1081,8 @@ openerp.testing.section('search.filters', {
var view = makeSearchView(instance);
return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
$fs.children(':eq(2)').trigger('click');
var $fs = view.$buttons.find('.filters-menu li a');
$fs.eq(2).trigger('click');
equal(view.query.length, 1, "click should have added a facet");
var facet = view.query.at(0);
equal(facet.values.length, 1, "facet should have a single value");
@@ -1111,8 +1097,8 @@ openerp.testing.section('search.filters', {
var view = makeSearchView(instance, {}, {foo2: true});
return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
$fs.children(':eq(2)').trigger('click');
var $fs = view.$buttons.find('.filters-menu li a');
$fs.eq(2).trigger('click');
equal(view.query.length, 1, "click should not have changed facet count");
var facet = view.query.at(0);
equal(facet.values.length, 2, "facet should have a second value");
@@ -1132,11 +1118,11 @@ openerp.testing.section('search.filters', {
});
return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
var $fs = view.$buttons.find('.filters-menu li a');
// sanity check
equal(view.query.length, 1, "query should have default facet");
strictEqual(calls, 0);
$fs.children(':eq(1)').trigger('click');
$fs.eq(1).trigger('click');
equal(view.query.length, 0, "click should have removed facet");
strictEqual(calls, 1, "one search should have been triggered");
});
@@ -1148,7 +1134,7 @@ openerp.testing.section('search.groupby', {
templates: true,
}, function (test) {
test('basic', {
asserts: 7,
asserts: 12,
setup: function (instance, $s, mock) {
mock('dummy.model:fields_view_get', function () {
return {
@@ -1168,22 +1154,20 @@ openerp.testing.section('search.groupby', {
var view = makeSearchView(instance);
return view.appendTo($fix)
.done(function () {
// 3 filters, 1 filtergroup group, 1 custom filter, 1 advanced, 1 Filters
// and 1 SaveFilter widget
equal(view.drawer.inputs.length, 8,
'should have 8 inputs total');
var group = _.find(view.drawer.inputs, function (f) {
return f instanceof instance.web.search.GroupbyGroup;
});
ok(group, "should have a GroupbyGroup input");
ok(group.getParent() === view.drawer,
"group's parent should be the drawer");
equal(view.search_fields.length, 0); // 0 fields
equal(view.filters.length, 0); // 0 filters (in filter menu)
equal(view.groupbys.length, 1); // 1 group of groupbys
var group = view.groupbys[0];
ok(group instanceof instance.web.search.GroupbyGroup,
'should have a GroupbyGroup input');
equal(group.filters.length, 3); // 3 group bys in group
ok(group.getParent() === view,
"group's parent should be the searchview");
group.toggle(group.filters[0]);
group.toggle(group.filters[2]);
var results = view.build_search_data();
deepEqual(results.errors, [], "should have no errors");
deepEqual(results.domains, [], "should have no domain");
deepEqual(results.contexts, [
new instance.web.CompoundContext(
@@ -1193,10 +1177,14 @@ openerp.testing.section('search.groupby', {
"{'group_by': 'foo'}",
"{'group_by': 'baz'}"
], "should have sequence of contexts");
ok(view.filter_menu instanceof instance.web.search.FilterMenu, 'should have a filter menu');
ok(view.groupby_menu instanceof instance.web.search.GroupByMenu, 'should have a group by menu');
ok(view.favorite_menu instanceof instance.web.search.FavoriteMenu, 'should have a favorite menu');
});
});
test('unified multiple groupby groups', {
asserts: 4,
asserts: 3,
setup: function (instance, $s, mock) {
mock('dummy.model:fields_view_get', function () {
return {
@@ -1218,14 +1206,7 @@ openerp.testing.section('search.groupby', {
var view = makeSearchView(instance);
return view.appendTo($fix)
.done(function () {
// 3 filters, 3 filtergroups, 1 custom filter, 1 advanced, 1 Filters,
// and 1 SaveFilter widget
equal(view.drawer.inputs.length, 10, "should have 10 inputs total");
var groups = _.filter(view.drawer.inputs, function (f) {
return f instanceof instance.web.search.GroupbyGroup;
});
var groups = view.groupbys;
equal(groups.length, 3, "should have 3 GroupbyGroups");
groups[0].toggle(groups[0].filters[0]);
@@ -1233,7 +1214,6 @@ openerp.testing.section('search.groupby', {
equal(view.query.length, 1,
"should have unified groupby groups in single facet");
deepEqual(view.build_search_data(), {
errors: [],
domains: [],
contexts: [new instance.web.CompoundContext(
"{'group_by': 'foo'}", "{'group_by': 'baz'}")],
@@ -1255,10 +1235,10 @@ openerp.testing.section('search.filters.saved', {
return view.appendTo($fix)
.done(function () {
var $span = $fix.find('.oe_searchview_custom_list span:first').click();
var $li = view.favorite_menu.$el.find('li:first').click();
ok($span.hasClass('badge'), "should check/select the filter's row");
ok($span.hasClass("oe_searchview_custom_private"),
ok($li.hasClass('selected'), "should check/select the filter's row");
ok($li.hasClass("oe_searchview_custom_private"),
"should have private filter note/class");
equal(view.query.length, 1, "should have only one facet");
var values = view.query.at(0).values;
@@ -1278,10 +1258,10 @@ openerp.testing.section('search.filters.saved', {
return view.appendTo($fix)
.done(function () {
var $row = $fix.find('.oe_searchview_custom li:first').click();
var $row = view.favorite_menu.$el.find('li:first').click();
view.query.remove(view.query.at(0));
ok(!$row.hasClass('badge'),
ok(!$row.hasClass('selected'),
"should not be checked anymore");
});
});
@@ -1293,7 +1273,7 @@ openerp.testing.section('search.filters.saved', {
return view.appendTo($fix)
.done(function () {
var $row = $fix.find('.oe_searchview_custom_list span:first').click();
var $row = view.favorite_menu.$el.find('li:first').click();
equal(view.query.length, 1, "should have one facet");
$row.click();
equal(view.query.length, 0, "should have removed facet");
@@ -1309,13 +1289,13 @@ openerp.testing.section('search.filters.saved', {
});
return view.appendTo($fix)
.done(function () {
$fix.find('.oe_searchview_custom_list span:eq(0)').click();
view.favorite_menu.$el.find('li:first').click();
equal(view.query.length, 1, "should have one facet");
deepEqual(
view.query.at(0).get('field').get_context(),
{'private': 1},
"should have selected first filter");
$fix.find('.oe_searchview_custom_list span:eq(1)').click();
view.favorite_menu.$el.find('li').eq(1).click();
equal(view.query.length, 1, "should have one facet");
deepEqual(
view.query.at(0).get('field').get_context(),
@@ -1338,10 +1318,8 @@ openerp.testing.section('search.filters.saved', {
});
return view.appendTo($fix)
.then(function () {
$fix.find('.oe_searchview_savefilter input#oe_searchview_custom_input')
.val("filter name")
.end()
.find('.oe_searchview_savefilter button').click();
view.favorite_menu.$el.find('.oe-save-name input').first().val("filter name");
view.favorite_menu.$el.find('.oe-save-name button').click();
return done.promise();
});
});
@@ -1356,16 +1334,16 @@ openerp.testing.section('search.advanced', {
return view.appendTo($fix)
.done(function () {
var $advanced = $fix.find('.oe_searchview_advanced');
var $filter_menu = view.filter_menu.$el;
// open advanced search (not actually useful)
$advanced.find('> h4').click();
$filter_menu.find('.oe-add-filter').click();
// select proposition (only one)
var $prop = $advanced.find('> form li:first');
var $prop = $filter_menu.find('.oe-filter-condition');
// field select should have two possible values, dummy and id
equal($prop.find('.searchview_extended_prop_field option').length,
equal($prop.find('select:first option').length,
2, "advanced search should provide choice between two fields");
// field should be dummy
equal($prop.find('.searchview_extended_prop_field').val(),
equal($prop.find('select:first').val(),
'dummy',
"only field should be dummy");
// operator should be "contains"/'ilike'
@@ -1375,8 +1353,7 @@ openerp.testing.section('search.advanced', {
$prop.find('.searchview_extended_prop_value input')
.val("stupid value");
// validate advanced search
$advanced.find('button.oe_apply').click();
$filter_menu.find('button.oe-apply-filter').click();
// resulting search
equal(view.query.length, 1, "search query should have a single facet");
var facet = view.query.at(0);
@@ -1392,34 +1369,35 @@ openerp.testing.section('search.advanced', {
return view.appendTo($fix)
.done(function () {
var $advanced = $fix.find('.oe_searchview_advanced');
var $filter_menu = view.filter_menu.$el;
// open advanced search (not actually useful)
$advanced.find('> h4').click();
$filter_menu.find('.oe-add-filter').click();
// open second condition
$advanced.find('button.oe_add_condition').click();
$filter_menu.find('a.oe-add-condition').click();
// select first proposition
var $prop1 = $advanced.find('> form li:first');
$prop1.find('.searchview_extended_prop_field').val('dummy').change();
$prop1.find('.searchview_extended_prop_op').val('ilike');
var $prop1 = $filter_menu.find('.oe-filter-condition:first');
$prop1.find('select.searchview_extended_prop_field').val("dummy").change();
$prop1.find('select.searchview_extended_prop_op').val('ilike');
$prop1.find('.searchview_extended_prop_value input')
.val("stupid value");
// select first proposition
var $prop2 = $advanced.find('> form li:last');
var $prop2 = $filter_menu.find('.oe-filter-condition:last');
// need to trigger event manually or op not changed
$prop2.find('.searchview_extended_prop_field').val('id').change();
$prop2.find('.searchview_extended_prop_op').val('=');
$prop2.find('select.searchview_extended_prop_field').val('id').change();
$prop2.find('select.searchview_extended_prop_op').val('=');
$prop2.find('.searchview_extended_prop_value input')
.val(42);
// validate advanced search
$advanced.find('button.oe_apply').click();
$filter_menu.find('button.oe-apply-filter').click();
// resulting search
equal(view.query.length, 1, "search query should have a single facet");
var facet = view.query.at(0);
ok(!facet.get('field').get_context(facet),
"advanced search facets should yield no context");
deepEqual(facet.get('field').get_domain(facet),
deepEqual(facet.get('field').get_domain(facet).eval(),
['|', ['dummy', 'ilike', "stupid value"],
['id', '=', 42]],
"advanced search facet should return proposed domain");
@@ -1427,179 +1405,5 @@ openerp.testing.section('search.advanced', {
});
// TODO: UI tests?
});
openerp.testing.section('search.invisible', {
dependencies: ['web.search'],
rpc: 'mock',
templates: true,
}, function (test) {
var registerTestField = function (instance, methods) {
instance.testing.TestWidget = instance.web.search.Field.extend(methods);
instance.web.search.fields.add('test', 'instance.testing.TestWidget');
};
var makeView = function (instance, mock, fields, arch, defaults) {
mock('ir.filters:get_filters', function () { return []; });
mock('test.model:fields_get', function () { return fields; });
mock('test.model:fields_view_get', function () {
return { type: 'search', fields: fields, arch: arch };
});
var ds = new instance.web.DataSet(null, 'test.model');
return new instance.web.SearchView(null, ds, false, defaults);
};
// Invisible fields should not auto-complete
test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) {
registerTestField(instance, {
complete: function () {
return $.when([{label: this.attrs.string}]);
},
});
var view = makeView(instance, mock, {
field0: {type: 'test', string: 'Field 0'},
field1: {type: 'test', string: 'Field 1'},
}, ['<search>',
'<field name="field0"/>',
'<field name="field1" modifiers="{&quot;invisible&quot;: true}"/>',
'</search>'].join(''));
return view.appendTo($fix)
.then(function () {
var done = $.Deferred();
view.complete_global_search({term: 'test'}, function (comps) {
done.resolve(comps);
});
return done;
}).then(function (completions) {
deepEqual(completions, [{label: 'Field 0'}],
"should only complete the visible field");
});
});
// Invisible filters should not appear in the drawer
test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) {
var view = makeView(instance, mock, {}, [
'<search>',
'<filter string="filter 0"/>',
'<filter string="filter 1" modifiers="{&quot;invisible&quot;: true}"/>',
'</search>'].join(''));
return view.appendTo($fix)
.then(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
strictEqual($fs.children().length,
1,
"should only display one filter");
strictEqual(_.str.trim($fs.children().text()),
"filter 0",
"should only display filter 0");
var done = $.Deferred();
view.complete_global_search({term: 'filter'}, function (comps) {
done.resolve();
strictEqual(comps.length, 1, "should only complete visible filter");
strictEqual(comps[0].label, "Filter on: filter 0",
"should complete filter 0");
});
return done;
});
});
test('invisible-previous-sibling', {asserts: 3}, function (instance, $fix, mock) {
var view = makeView(instance, mock, {}, [
'<search>',
'<filter string="filter 0" context="{&quot;test&quot;: 0}"/>',
'<filter string="filter 1" modifiers="{&quot;invisible&quot;: true}" context="{&quot;test&quot;: 1}"/>',
'<filter string="filter 2" modifiers="{&quot;invisible&quot;: true}" context="{&quot;test&quot;: 2}"/>',
'<filter string="filter 3" context="{&quot;test&quot;: 3}"/>',
'</search>'].join(''));
return view.appendTo($fix)
.done(function () {
// Select filter 3
$fix.find('.oe_searchview_filters ul li:contains("filter 3")').click();
equal(view.query.length, 1, "should have selected a filter");
var facet = view.query.at(0);
strictEqual(facet.values.at(0).get('label'), "filter 3",
"should have correctly labelled the facet");
deepEqual(view.build_search_data().contexts, [{test: 3}],
"should have built correct context");
});
});
// Invisible filter groups should not appear in the drawer
// Group invisibility should be inherited by children
test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) {
registerTestField(instance, {
complete: function () {
return $.when([{label: this.attrs.string}]);
},
});
var view = makeView(instance, mock, {
field0: {type: 'test', string: 'Field 0'},
field1: {type: 'test', string: 'Field 1'},
}, [
'<search>',
'<group string="Visibles">',
'<field name="field0"/>',
'<filter string="Filter 0"/>',
'</group>',
'<group string="Invisibles" modifiers="{&quot;invisible&quot;: true}">',
'<field name="field1"/>',
'<filter string="Filter 1"/>',
'</group>',
'</search>'
].join(''));
return view.appendTo($fix)
.then(function () {
strictEqual($fix.find('.oe_searchview_filters dt').length,
1,
"should only display one group");
strictEqual($fix.find('.oe_searchview_filters dt').text(),
'w Visibles',
"should only display the Visibles group (and its icon char)");
var $fs = $fix.find('.oe_searchview_filters ul');
strictEqual($fs.children().length, 1,
"should only have one filter in the drawer");
strictEqual(_.str.trim($fs.text()), "Filter 0",
"should have filter 0 as sole filter");
var done = $.Deferred();
view.complete_global_search({term: 'filter'}, function (compls) {
done.resolve();
console.log("completions", compls);
strictEqual(compls.length, 5,
"should have 5 completions"); // 2 filters and 3 separators
deepEqual(_.pluck(compls, 'label'),
[undefined, 'Field 0', 'Filter on: Filter 0', undefined, undefined],
"should complete on field 0 and filter 0");
});
return done;
});
});
// Default on invisible fields should still work, for fields and filters both
test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) {
var view = makeView(instance, mock, {
field: {type: 'char', string: "Field"},
field2: {type: 'char', string: "Field 2"},
}, [
'<search>',
'<field name="field2"/>',
'<filter name="filter2" string="Filter"',
' domain="[[\'qwa\', \'=\', 42]]"/>',
'<group string="Invisibles" modifiers="{&quot;invisible&quot;: true}">',
'<field name="field"/>',
'<filter name="filter" string="Filter"',
' domain="[[\'whee\', \'=\', \'42\']]"/>',
'</group>',
'</search>'
].join(''), {field: "foo", filter: true});
return view.appendTo($fix)
.then(function () {
deepEqual(view.build_search_data(), {
errors: [],
groupbys: [],
contexts: [],
domains: [
// Generated from field
[['field', 'ilike', 'foo']],
// generated from filter
"[['whee', '=', '42']]"
],
}, "should yield invisible fields selected by defaults");
});
});
});
+1
View File
@@ -106,6 +106,7 @@
<script type="text/javascript" src="/web/static/src/js/view_list.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_form.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_list_editable.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_pivot.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_tree.js"></script>
<script type="text/javascript" src="/base/static/src/js/apps.js"></script>
@@ -88,6 +88,8 @@ openerp.web_calendar = function(instance) {
this.range_start = null;
this.range_stop = null;
this.selected_filters = [];
this.shown = $.Deferred();
},
set_default_options: function(options) {
@@ -229,6 +231,7 @@ openerp.web_calendar = function(instance) {
this.info_fields.push(fv.arch.children[fld].attrs.name);
}
self.shown.done(this._do_show_init.bind(this));
var edit_check = new instance.web.Model(this.dataset.model)
.call("check_access_rights", ["write", false])
.then(function (write_right) {
@@ -238,15 +241,18 @@ openerp.web_calendar = function(instance) {
.call("check_access_rights", ["create", false])
.then(function (create_right) {
self.create_right = create_right;
self.init_calendar().then(function() {
$(window).trigger('resize');
self.trigger('calendar_view_loaded', fv);
self.ready.resolve();
});
self.ready.resolve();
self.trigger('calendar_view_loaded', fv);
});
return $.when(edit_check, init);
},
_do_show_init: function () {
var self = this;
this.init_calendar().then(function() {
$(window).trigger('resize');
self.trigger('calendar_view_loaded', self.fields_view);
});
},
get_fc_init_options: function () {
//Documentation here : http://arshaw.com/fullcalendar/docs/
var self = this;
@@ -647,7 +653,13 @@ openerp.web_calendar = function(instance) {
return data;
},
do_search: function(domain, context, _group_by) {
do_search: function (domain, context, _group_by) {
var self = this;
this.shown.done(function () {
self._do_search(domain, context, _group_by);
});
},
_do_search: function(domain, context, _group_by) {
var self = this;
if (! self.all_filters) {
self.all_filters = {}
@@ -854,11 +866,12 @@ openerp.web_calendar = function(instance) {
return false;
},
do_show: function() {
do_show: function() {
if (this.$buttons) {
this.$buttons.show();
}
this.do_push_state({});
this.shown.resolve();
return this._super();
},
do_hide: function () {
+21 -8
View File
@@ -31,6 +31,14 @@ instance.web_graph.GraphView = instance.web.View.extend({
get_domain: function () {},
get_groupby: function () {},
};
this.$buttons = options.$buttons;
this.view_loaded = $.Deferred();
},
start: function() {
var $buttons = instance.web.qweb.render("graph-view-buttons");
this.$buttons.html($buttons);
return this._super();
},
view_loading: function (fields_view_get) {
@@ -45,6 +53,7 @@ instance.web_graph.GraphView = instance.web.View.extend({
row_groupby: [],
col_groupby: [],
graph_view: this,
$buttons: this.$buttons
};
_.each(arch.children, function (field) {
@@ -72,9 +81,17 @@ instance.web_graph.GraphView = instance.web.View.extend({
if (self.widget_config.measures.length === 0) {
self.widget_config.measures.push('__count');
}
this.view_loaded.resolve();
},
do_search: function (domain, context, group_by) {
var self = this;
this.view_loaded.done(function () {
self._do_search(domain, context, group_by);
});
},
_do_search: function (domain, context, group_by) {
if (this.ignore_do_search) {
this.ignore_do_search = false;
return;
@@ -97,12 +114,8 @@ instance.web_graph.GraphView = instance.web.View.extend({
this.widget_config.measures = measures;
}
this.graph_widget = new openerp.web_graph.Graph(this, this.model, domain, this.widget_config);
this.graph_widget.appendTo(this.$el);
this.ViewManager.on('switch_mode', this, function (e) {
if (e === 'graph') {
var group_bys = self.get_groupbys_from_searchview();
this.graph_widget.set(domain, group_bys.group_by, group_bys.col_group_by, group_bys.measures);
}
this.graph_widget.appendTo(this.$el).done(function () {
});
return;
}
@@ -209,7 +222,7 @@ instance.web_graph.GraphView = instance.web.View.extend({
return {
category:'GroupBy',
values: this.make_groupby_values(groupbys, 'group_by'),
icon:'w',
icon:'fa fa-bars',
field: this.search_view._s_groupby
};
},
@@ -218,7 +231,7 @@ instance.web_graph.GraphView = instance.web.View.extend({
return {
category:'ColGroupBy',
values: this.make_groupby_values(groupbys, 'col_group_by'),
icon:'f',
icon:'fa fa-level-down',
field: this.col_search_field
};
},
+32 -36
View File
@@ -20,12 +20,12 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
this.model = model;
this.domain = domain;
this.mode = options.mode || 'pivot'; // pivot, bar, pie, line
this.heatmap_mode = options.heatmap_mode || 'none';
this.visible_ui = options.visible_ui || true;
this.bar_ui = options.bar_ui || 'group';
this.graph_view = options.graph_view || null;
this.pivot_options = options;
this.title = options.title || 'Data';
this.$buttons = options.$buttons;
},
start: function() {
@@ -33,8 +33,28 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
this.table = $('<table>');
this.$('.graph_main_content').append(this.table);
this.$buttons.find('.oe-pivot-mode').click(function () {
self.set_mode.bind(self)('pivot');
});
this.$measure_list = this.$buttons.find('.oe-measure-list');
this.$buttons.find('.oe-bar-mode').click(function () {
self.set_mode.bind(self)('bar');
});
this.$buttons.find('.oe-line-mode').click(function () {
self.set_mode.bind(self)('line');
});
this.$buttons.find('.oe-pie-mode').click(function () {
self.set_mode.bind(self)('pie');
});
this.$buttons.find('.fa-expand').click(this.swap_axis.bind(this));
this.$buttons.find('.fa-arrows-alt').click(function () {
self.pivot.expand_all().then(self.proxy('display_data'));
});
this.$buttons.find('.fa-download').click(this.export_xls.bind(this));
var indexes = {'pivot': 0, 'bar': 1, 'line': 2, 'chart': 3};
this.$('.graph_mode_selection label').eq(indexes[this.mode]).addClass('active');
this.$('.graph_mode_selection label').eq(indexes[this.mode]).addClass('selected');
if (this.mode !== 'pivot') {
this.$('.graph_heatmap label').addClass('disabled');
@@ -42,7 +62,6 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
} else {
this.$('.graph_main_content').addClass('graph_pivot_mode');
}
// get search view
var parent = this.getParent();
while (!(parent instanceof openerp.web.ViewManager)) {
@@ -103,6 +122,11 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
// this method gets the fields that appear in the search view, under the
// 'Groupby' heading
get_search_fields: function () {
// this method is disabled for now. This requires extensive changes because the
// search view works quite differently: But the graph view is going to be split
// soon in pivot view and graph view. The pivot view will then properly handle
// groupbys.
return [];
var self = this;
var groupbygroups = _(this.search_view.drawer.inputs).select(function (g) {
@@ -142,12 +166,13 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
},
add_measures_to_options: function() {
this.$('.graph_measure_selection').append(
this.$measure_list.append(
_.map(this.measure_list, function (measure) {
return $('<li>').append($('<a>').attr('data-choice', measure.field)
.attr('href', '#')
.text(measure.string));
}));
this.$measure_list.find('li').click(this.measure_selection.bind(this));
},
// ----------------------------------------------------------------------
@@ -212,15 +237,6 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
this.display_data();
},
set_heatmap_mode: function (mode) { // none, row, col, all
this.heatmap_mode = mode;
if (mode === 'none') {
this.$('.graph_heatmap label').removeClass('disabled');
this.$('.graph_heatmap label').removeClass('active');
}
this.display_data();
},
create_field_value: function (f) {
var field = (_.contains(f, ':')) ? f.split(':')[0] : f,
groupby_field = _.findWhere(this.groupby_fields, {field:field}),
@@ -281,11 +297,11 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
put_measure_checkmarks: function () {
var self = this,
measures_li = this.$('.graph_measure_selection a');
measures_li.removeClass('oe_selected');
measures_li = this.$measure_list.find('li');
measures_li.removeClass('selected');
_.each(this.measure_list, function (measure, index) {
if (_.findWhere(self.pivot.measures, measure)) {
measures_li.eq(index).addClass('oe_selected');
measures_li.eq(index).addClass('selected');
}
});
@@ -309,17 +325,6 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
}
},
heatmap_mode_selection: function (event) {
event.preventDefault();
var mode = event.currentTarget.getAttribute('data-mode');
if (this.heatmap_mode === mode) {
event.stopPropagation();
this.set_heatmap_mode('none');
} else {
this.set_heatmap_mode(mode);
}
},
header_cell_clicked: function (event) {
event.preventDefault();
event.stopPropagation();
@@ -497,14 +502,6 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
var formatted_value = raw && !_.isUndefined(value) ? value : openerp.web.format_value(value, {type:this.pivot.measures[index].type}),
cell = {value:formatted_value};
if (this.heatmap_mode === 'none') { return cell; }
var total = (this.heatmap_mode === 'both') ? this.pivot.get_total()[index]
: (this.heatmap_mode === 'row') ? this.pivot.get_total(row)[index]
: this.pivot.get_total(col)[index];
var color = Math.floor(90 + 165*(total - Math.abs(value))/total);
if (color < 255) {
cell.color = color;
}
return cell;
},
@@ -570,7 +567,6 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({
this.$('.graph_main_content svg').remove();
this.$('.graph_main_content div').remove();
this.table.empty();
this.table.toggleClass('heatmap', this.heatmap_mode !== 'none');
this.$('.graph_options_selection label').last().toggleClass('disabled', this.pivot.no_data);
this.width = this.$el.width();
this.height = Math.min(Math.max(document.documentElement.clientHeight - 116 - 60, 250), Math.round(0.8*this.$el.width()));
+20 -54
View File
@@ -1,59 +1,25 @@
<templates>
<t t-name="graph-view-buttons">
<div class="btn-group btn-group-sm">
<button class="btn btn-default fa fa-table oe-pivot-mode"></button>
<button class="btn btn-default fa fa-bar-chart-o oe-bar-mode"></button>
<button class="btn btn-default fa fa-line-chart oe-line-mode"></button>
<button class="btn btn-default fa fa-pie-chart oe-pie-mode"></button>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-default fa fa-expand oe-pivot"></button>
<button class="btn btn-default fa fa-arrows-alt oe-pivot"></button>
<button class="btn btn-default fa fa-download oe-pivot"></button>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
Measures <span class="caret"/>
</button>
<ul class="oe-measure-list dropdown-menu"/>
</div>
</t>
<div t-name="GraphWidget" class="graph_widget">
<div class="graph_header">
<div class="btn-toolbar hidden-print" role="toolbar">
<div class="btn-group graph_mode_selection" data-toggle="buttons">
<label class="btn btn-default" data-mode="pivot" title="Table Mode">
<input type="radio" name="modes"/><span class="fa fa-table"></span>
</label>
<label class="btn btn-default" data-mode="bar" title="Bar Chart">
<input type="radio" name="modes"/><span class="fa fa-bar-chart-o"></span>
</label>
<label class="btn btn-default" data-mode="line" title="Line Chart">
<input type="radio" name="modes"/><span class="fa fa-check-square"></span>
</label>
<label class="btn btn-default" data-mode="pie" title="Pie Chart">
<input type="radio" name="modes"/><span class="fa fa-adjust"></span>
</label>
</div>
<div class="btn-group graph_heatmap" data-toggle="buttons" >
<label class="btn btn-default" data-mode="both" title="Heat Map">
<input type="radio" name="heatmap"/><span class="fa fa-th"></span>
</label>
<label class="btn btn-default" data-mode="row" title="Heat Map (rows)">
<input type="radio" name="heatmap"/><span class="fa fa-bars"></span>
</label>
<label class="btn btn-default" data-mode="col" title="Heat Map (columns)">
<input type="radio" name="heatmap"/><span class="fa fa-bars fa-rotate-90"></span>
</label>
</div>
<div class="btn-group graph_options_selection">
<label class="btn btn-default" data-choice="swap_axis" title="Swap Axis">
<span class="fa fa-expand"></span>
</label>
<label class="btn btn-default" data-choice="expand_all" title="Expand All">
<span class="fa fa-arrows-alt"></span>
</label>
<label class="btn btn-default" data-choice="update_values" title="Reload Data">
<span class="fa fa-refresh"></span>
</label>
<label class="btn btn-default" data-choice="export_data" title="Export Data" style="display:none">
<span class="fa fa-download"></span>
</label>
</div>
<div class="btn-group">
<label class="btn btn-default dropdown-toggle" data-toggle="dropdown">
Measures <span class="caret"></span>
</label>
<ul class="dropdown-menu graph_measure_selection" role="menu">
</ul>
</div>
</div>
</div>
<div class="graph_main_content"></div>
</div>
+12 -10
View File
@@ -43,6 +43,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
this.currently_dragging = {};
this.limit = options.limit || 40;
this.add_group_mutex = new $.Mutex();
if (!this.options.$buttons || !this.options.$buttons.length) {
this.options.$buttons = false;
}
},
view_loading: function(r) {
return this.load_kanban(r);
@@ -69,13 +72,12 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
if (unsorted && default_order) {
this.dataset.set_sort(default_order.split(','));
}
this.$el.addClass(this.fields_view.arch.attrs['class']);
this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
if (this.options.$buttons) {
this.$buttons.appendTo(this.options.$buttons);
} else {
this.$el.find('.oe_kanban_buttons').replaceWith(this.$buttons);
this.$('.oe_kanban_buttons').replaceWith(this.$buttons);
}
this.$buttons
.on('click', 'button.oe_kanban_button_new', this.do_add_record)
@@ -252,12 +254,12 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields));
}
var grouping = new instance.web.Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields);
return self.alive($.when(grouping)).done(function(groups) {
return self.alive($.when(grouping)).then(function(groups) {
self.remove_no_result();
if (groups) {
self.do_process_groups(groups);
return self.do_process_groups(groups);
} else {
self.do_process_dataset();
return self.do_process_dataset();
}
});
});
@@ -266,12 +268,12 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
var self = this;
this.$el.find('table:first').show();
this.$el.removeClass('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
this.add_group_mutex.exec(function() {
return this.add_group_mutex.exec(function() {
self.do_clear_groups();
self.dataset.ids = [];
if (!groups.length) {
self.no_result();
return false;
return $.when();
}
self.nb_records = 0;
var remaining = groups.length - 1,
@@ -306,8 +308,8 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
var self = this;
this.$el.find('table:first').show();
this.$el.removeClass('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
var def = $.Deferred();
this.add_group_mutex.exec(function() {
var def = $.Deferred();
self.do_clear_groups();
self.dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }).done(function(records) {
var kgroup = new instance.web_kanban.KanbanGroup(self, records, null, self.dataset);
@@ -326,8 +328,8 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
}).done(null, function() {
def.reject();
});
return def;
});
return def;
},
do_reload: function() {
this.do_search(this.search_domain, this.search_context, this.search_group_by);
@@ -490,7 +492,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
},
do_show: function() {
if (this.$buttons) {
if (this.options.$buttons) {
this.$buttons.show();
}
this.do_push_state({});
@@ -15,13 +15,13 @@
<div t-name="KanbanView.buttons" class="oe_kanban_buttons">
<t t-if="widget.options.action_buttons !== false">
<t t-if="widget.is_action_enabled('create')">
<button type="button" class="oe_kanban_button_new oe_highlight">
<button type="button" class="oe_kanban_button_new btn btn-primary btn-sm">
<t t-esc="widget.options.create_text || _t('Create')"/>
</button>
<span t-if="widget.is_action_enabled('group_create')" class="oe_alternative" style="display: none">
<span class="oe_fade">or</span>
<div class="oe_bounce_container">
<a href="#" class="oe_bold oe_kanban_add_column">Add a new column</a>
<a href="#" class="oe_kanban_add_column text-info">Add a new column</a>
</div>
</span>
</t>
@@ -1,3 +1,7 @@
.oe_bunchaforms > div {
float: left;
}
body.openerp {
overflow: auto;
}
@@ -1,10 +1,10 @@
openerp.web_view_editor = function(instance) {
var _t = instance.web._t;
var QWeb = instance.web.qweb;
instance.web.ViewManagerAction.include({
instance.web.ViewManager.include({
on_debug_changed:function(evt){
var val = $(evt.currentTarget).find('option:selected').val(),
current_view = this.views[this.active_view].controller;
var val = $(evt.target).data('action'),
current_view = this.active_view.controller;
if(val === "manage_views"){
if (current_view.fields_view && current_view.fields_view.arch) {
var view_editor = new instance.web_view_editor.ViewEditor(current_view, current_view.$el, this.dataset, current_view.fields_view.arch);
@@ -13,7 +13,6 @@ instance.web.ViewManagerAction.include({
this.do_warn(_t("Manage Views"),
_t("Could not find current view declaration"));
}
evt.currentTarget.selectedIndex = 0;
}else{
return this._super.apply(this,arguments);
}
@@ -51,6 +50,7 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({
views_switcher: false,
action_buttons: false,
search_view: false,
headless: true,
pager: false,
radio: true,
select_view_id: self.parent.fields_view.view_id
@@ -71,7 +71,7 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({
this.action_manager.appendTo(this.view_edit_dialog.$el);
$.when(this.action_manager.do_action(action)).done(function() {
var viewmanager = self.action_manager.inner_widget;
var controller = viewmanager.views[viewmanager.active_view].controller;
var controller = viewmanager.active_view.controller;
$(controller.groups).bind({
'selected': function (e, ids, records, deselected) {
self.main_view_id = ids[0];
@@ -408,7 +408,7 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({
action_manager.do_action(action);
}},
{text: _t("Close"), click: function(){
self.action_manager.inner_widget.views[self.action_manager.inner_widget.active_view].controller.reload_content();
self.action_manager.inner_widget.active_view.controller.reload_content();
self.edit_xml_dialog.close();
}}
]
+1
View File
@@ -186,6 +186,7 @@ class view(osv.osv):
('tree','Tree'),
('form','Form'),
('graph', 'Graph'),
('pivot', 'Pivot'),
('calendar', 'Calendar'),
('diagram','Diagram'),
('gantt', 'Gantt'),
+11
View File
@@ -671,6 +671,15 @@
</rng:element>
</rng:define>
<rng:define name="pivot">
<rng:element name="pivot">
<rng:optional><rng:attribute name="string" /></rng:optional>
<rng:zeroOrMore>
<rng:ref name="field"/>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="button">
<rng:element name="button">
<rng:ref name="overload"/>
@@ -742,6 +751,7 @@
<rng:ref name="tree" />
<rng:ref name="notebook" />
<rng:ref name="graph" />
<rng:ref name="pivot" />
<rng:ref name="calendar" />
<rng:ref name="gantt" />
<rng:ref name="xpath" />
@@ -769,6 +779,7 @@
<rng:ref name="tree" />
<rng:ref name="notebook" />
<rng:ref name="graph" />
<rng:ref name="pivot" />
<rng:ref name="calendar" />
<rng:ref name="data" />
<rng:ref name="gantt" />