[MERGE] [ADD] web: form: added FieldCharDomain widget. This widget works on a char field and is used to manage a domain. It allows to select records in a list view and to store the domain in the field, without having to deal with the complexity of writing domains.

[ADD] web: form: added FieldBarChart widget. This widget works on char field and display a serialized list of values as a barchart. This is used in stat buttons, like percent pie or stat info, to display a summary of some data on form view.

[IMP] web: some fixes and improvements in the stat buttons.

[IMP] web: list view: buttons in list view can now be text button, not only icon-based buttons. Icons are so v6 and not swag.

bzr revid: tde@openerp.com-20140416114938-3qsv8b8dumw5x4pj
This commit is contained in:
Thibault Delavallée 2014-04-16 13:49:38 +02:00
commit 4f53557311
5 changed files with 183 additions and 21 deletions

View File

@ -1,4 +1,4 @@
@charset "UTF-8"; @charset "utf-8";
@font-face { @font-face {
font-family: "mnmliconsRegular"; font-family: "mnmliconsRegular";
src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot"); src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot");
@ -352,9 +352,17 @@
width: 37px; width: 37px;
text-align: center; text-align: center;
} }
.openerp .oe_button_box .oe_stat_button .oe_form_field_percent_pie {
width: 42px;
}
.openerp .oe_button_box .oe_stat_button .oe_form_field_bar_chart {
width: 42px;
}
.openerp .oe_button_box .oe_stat_button svg { .openerp .oe_button_box .oe_stat_button svg {
width: 38px; width: 38px;
height: 38px; height: 38px;
display: inline;
vertical-align: middle;
} }
.openerp .oe_avatar > img { .openerp .oe_avatar > img {
max-height: 90px; max-height: 90px;
@ -2768,7 +2776,7 @@
padding: 3px 6px; padding: 3px 6px;
white-space: pre-line; white-space: pre-line;
} }
.openerp .oe_list_content > tbody > tr > td > button, .openerp .oe_list_content > tbody > tr > th > button { .openerp .oe_list_content > tbody > tr > td > button.btn_img, .openerp .oe_list_content > tbody > tr > th > button.btn_img {
border: none; border: none;
background: transparent; background: transparent;
padding: 0; padding: 0;

View File

@ -362,9 +362,15 @@ $sheet-padding: 16px
padding: 0px 3px padding: 0px 3px
width: 37px width: 37px
text-align: center text-align: center
.oe_form_field_percent_pie
width: 42px
.oe_form_field_bar_chart
width: 42px
svg svg
width: 38px width: 38px
height: 38px height: 38px
display: inline
vertical-align: middle
.oe_avatar .oe_avatar
> img > img
max-height: 90px max-height: 90px
@ -2241,7 +2247,7 @@ $sheet-padding: 16px
padding: 3px 6px padding: 3px 6px
white-space: pre-line white-space: pre-line
> td, > th > td, > th
> button > button.btn_img
border: none border: none
background: transparent background: transparent
padding: 0 padding: 0

View File

@ -2438,6 +2438,76 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
} }
}); });
instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
init: function(field_manager, node) {
this._super.apply(this, arguments);
},
start: function() {
var self = this;
this._super.apply(this, arguments);
this.on("change:effective_readonly", this, function () {
this.display_field();
this.render_value();
});
this.display_field();
return this._super();
},
render_value: function() {
this.$('button.select_records').css('visibility', this.get('effective_readonly') ? 'hidden': '');
},
set_value: function(value_) {
var self = this;
this.set('value', value_ || false);
this.display_field();
},
display_field: function() {
var self = this;
this.$el.html(instance.web.qweb.render("FieldCharDomain", {widget: this}));
if (this.get('value')) {
var domain = instance.web.pyeval.eval('domain', this.get('value'));
var relation = this.getParent().fields.mailing_model.get('value')[0];
var ds = new instance.web.DataSetStatic(self, relation, self.build_context());
ds.call('search_count', [domain]).then(function (results) {
$('.oe_domain_count', self.$el).text(results + ' records selected');
$('button span', self.$el).text(' Change selection');
});
} else {
$('.oe_domain_count', this.$el).text('0 record selected');
$('button span', this.$el).text(' Select records');
};
this.$('.select_records').on('click', self.on_click);
},
on_click: function(ev) {
var self = this;
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
this.pop = new instance.web.form.SelectCreatePopup(this);
this.pop.select_element(
model, {title: 'Select records...'},
[], this.build_context());
this.pop.on("elements_selected", self, function(element_ids) {
if (this.pop.$('input.oe_list_record_selector').prop('checked')) {
var search_data = this.pop.searchview.build_search_data();
var domain_done = instance.web.pyeval.eval_domains_and_contexts({
domains: search_data.domains,
contexts: search_data.contexts,
group_by_seq: search_data.groupbys || []
}).then(function (results) {
return results.domain;
});
}
else {
var domain = ["id", "in", element_ids];
var domain_done = $.Deferred().resolve(domain);
}
$.when(domain_done).then(function (domain) {
var domain = self.pop.dataset.domain.concat(domain || []);
self.set_value(JSON.stringify(domain))
});
});
event.preventDefault();
},
});
instance.web.DateTimeWidget = instance.web.Widget.extend({ instance.web.DateTimeWidget = instance.web.Widget.extend({
template: "web.datepicker", template: "web.datepicker",
jqueryui_object: 'datetimepicker', jqueryui_object: 'datetimepicker',
@ -2833,10 +2903,10 @@ instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
svg.innerHTML = ""; svg.innerHTML = "";
nv.addGraph(function() { nv.addGraph(function() {
var size=43; var width = 42, height = 42;
var chart = nv.models.pieChart() var chart = nv.models.pieChart()
.width(size) .width(width)
.height(size) .height(height)
.margin({top: 0, right: 0, bottom: 0, left: 0}) .margin({top: 0, right: 0, bottom: 0, left: 0})
.donut(true) .donut(true)
.showLegend(false) .showLegend(false)
@ -2849,11 +2919,11 @@ instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
.datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}]) .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
.transition() .transition()
.call(chart) .call(chart)
.attr({width:size, height:size}); .attr('style', 'width: ' + width + 'px; height:' + height + 'px;');
d3.select(svg) d3.select(svg)
.append("text") .append("text")
.attr({x: size/2, y: size/2 + 3, 'text-anchor': 'middle'}) .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'})
.style({"font-size": "10px", "font-weight": "bold"}) .style({"font-size": "10px", "font-weight": "bold"})
.text(formatted_value); .text(formatted_value);
@ -2863,6 +2933,43 @@ instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
} }
}); });
/**
The FieldBarChart expectsa list of values (indeed)
*/
instance.web.form.FieldBarChart = instance.web.form.AbstractField.extend({
template: 'FieldBarChart',
render_value: function() {
var value = JSON.parse(this.get('value'));
var svg = this.$('svg')[0];
svg.innerHTML = "";
nv.addGraph(function() {
var width = 34, height = 34;
var chart = nv.models.discreteBarChart()
.x(function (d) { return d.tooltip })
.y(function (d) { return d.value })
.width(width)
.height(height)
.margin({top: 0, right: 0, bottom: 0, left: 0})
.tooltips(false)
.showValues(false)
.transitionDuration(350)
.showXAxis(false)
.showYAxis(false);
d3.select(svg)
.datum([{key: 'values', values: value}])
.transition()
.call(chart)
.attr('style', 'width: ' + (width + 4) + 'px; height: ' + (height + 8) + 'px;');
nv.utils.windowResize(chart.update);
return chart;
});
}
});
instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
@ -3419,7 +3526,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
} }
self.floating = false; self.floating = false;
} }
if (used && self.get("value") === false && ! self.no_ed) { if (used && self.get("value") === false && ! self.no_ed && (self.options.no_create === false || self.options.no_create === undefined)) {
self.ed_def.reject(); self.ed_def.reject();
self.uned_def.reject(); self.uned_def.reject();
self.ed_def = $.Deferred(); self.ed_def = $.Deferred();
@ -5919,20 +6026,30 @@ instance.web.form.X2ManyCounter = instance.web.form.AbstractField.extend(instanc
display a simple string "<value of field> <label of the field>" display a simple string "<value of field> <label of the field>"
*/ */
instance.web.form.StatInfo = instance.web.form.AbstractField.extend({ instance.web.form.StatInfo = instance.web.form.AbstractField.extend({
is_field_number: true,
init: function() { init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.set("value", 0); this.internal_set_value(0);
},
set_value: function(value_) {
if (value_ === false || value_ === undefined) {
value_ = 0;
}
this._super.apply(this, [value_]);
}, },
render_value: function() { render_value: function() {
var options = { var options = {
value: this.get("value") || 0, value: this.get("value") || 0,
text: this.string,
}; };
if (! this.node.attrs.nolabel) {
options.text = this.string
}
this.$el.html(QWeb.render("StatInfo", options)); this.$el.html(QWeb.render("StatInfo", options));
}, },
}); });
/** /**
* Registry of form fields, called by :js:`instance.web.FormView`. * Registry of form fields, called by :js:`instance.web.FormView`.
* *
@ -5946,6 +6063,7 @@ instance.web.form.widgets = new instance.web.Registry({
'url' : 'instance.web.form.FieldUrl', 'url' : 'instance.web.form.FieldUrl',
'text' : 'instance.web.form.FieldText', 'text' : 'instance.web.form.FieldText',
'html' : 'instance.web.form.FieldTextHtml', 'html' : 'instance.web.form.FieldTextHtml',
'char_domain': 'instance.web.form.FieldCharDomain',
'date' : 'instance.web.form.FieldDate', 'date' : 'instance.web.form.FieldDate',
'datetime' : 'instance.web.form.FieldDatetime', 'datetime' : 'instance.web.form.FieldDatetime',
'selection' : 'instance.web.form.FieldSelection', 'selection' : 'instance.web.form.FieldSelection',
@ -5961,6 +6079,7 @@ instance.web.form.widgets = new instance.web.Registry({
'boolean' : 'instance.web.form.FieldBoolean', 'boolean' : 'instance.web.form.FieldBoolean',
'float' : 'instance.web.form.FieldFloat', 'float' : 'instance.web.form.FieldFloat',
'percentpie': 'instance.web.form.FieldPercentPie', 'percentpie': 'instance.web.form.FieldPercentPie',
'barchart': 'instance.web.form.FieldBarChart',
'integer': 'instance.web.form.FieldFloat', 'integer': 'instance.web.form.FieldFloat',
'float_time': 'instance.web.form.FieldFloat', 'float_time': 'instance.web.form.FieldFloat',
'progressbar': 'instance.web.form.FieldProgressBar', 'progressbar': 'instance.web.form.FieldProgressBar',

View File

@ -2261,8 +2261,8 @@ instance.web.list.Button = instance.web.list.Column.extend({
attrs = this.modifiers_for(row_data); attrs = this.modifiers_for(row_data);
} }
if (attrs.invisible) { return ''; } if (attrs.invisible) { return ''; }
var template = this.icon && 'ListView.row.button' || 'ListView.row.text_button';
return QWeb.render('ListView.row.button', { return QWeb.render(template, {
widget: this, widget: this,
prefix: instance.session.prefix, prefix: instance.session.prefix,
disabled: attrs.readonly disabled: attrs.readonly

View File

@ -612,10 +612,16 @@
<div class="oe_sidebar"> <div class="oe_sidebar">
<t t-foreach="widget.sections" t-as="section"> <t t-foreach="widget.sections" t-as="section">
<div class="oe_form_dropdown_section"> <div class="oe_form_dropdown_section">
<button class="oe_dropdown_toggle oe_dropdown_arrow"> <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-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/>
<t t-esc="section.label"/> <t t-esc="section.label"/>
</button> </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"> <ul class="oe_dropdown_menu">
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname"> <li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<t t-if="section.name == 'files'"> <t t-if="section.name == 'files'">
@ -792,11 +798,16 @@
</span> </span>
</t> </t>
</t> </t>
<button t-name="ListView.row.text_button" type="button"
t-att-title="widget.string" t-att-disabled="disabled || undefined"
t-att-class="disabled ? 'oe_list_button_disabled btn' : 'btn'">
<t t-esc="widget.string"/>
</button>
<button t-name="ListView.row.button" type="button" <button t-name="ListView.row.button" type="button"
t-att-title="widget.string" t-att-disabled="disabled || undefined" t-att-title="widget.string" t-att-disabled="disabled || undefined"
t-att-class="disabled ? 'oe_list_button_disabled' : undefined" t-att-class="disabled ? 'oe_list_button_disabled btn_img' : 'btn_img'"
><img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png" ><img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png"
t-att-alt="widget.string"/></button> t-att-alt="widget.string"/></button>
<t t-extend="ListView.row"> <t t-extend="ListView.row">
<!-- adds back padding to row being rendered after edition, if necessary <!-- adds back padding to row being rendered after edition, if necessary
(if not deletable add back padding), otherwise the row being added is (if not deletable add back padding), otherwise the row being added is
@ -1069,6 +1080,16 @@
</t> </t>
</div> </div>
</t> </t>
<t t-name="FieldCharDomain">
<div class="oe_form_field">
<span class="oe_domain_count"/>
<button class="oe_button oe_link select_records" type="button"
t-att-style="widget.node.attrs.style"
t-att-accesskey="widget.node.attrs.accesskey">
<span class="fa fa-arrow-right"/>
</button>
</div>
</t>
<t t-name="web.datepicker"> <t t-name="web.datepicker">
<span> <span>
<t t-set="placeholder" t-value="widget.getParent().node and widget.getParent().node.attrs.placeholder"/> <t t-set="placeholder" t-value="widget.getParent().node and widget.getParent().node.attrs.placeholder"/>
@ -1209,9 +1230,16 @@
</span> </span>
</t> </t>
<t t-name="FieldPercentPie"> <t t-name="FieldPercentPie">
<span class="oe_form_field oe_form_field_percent_pie" t-att-style="widget.node.attrs.style"> <div class="oe_form_field oe_form_field_percent_pie" t-att-style="widget.node.attrs.style">
<svg></svg> <svg></svg>
</span> <span t-if="widget.string"><t t-esc="widget.string"/></span>
</div>
</t>
<t t-name="FieldBarChart">
<div class="oe_form_field oe_form_field_bar_chart" t-att-style="widget.node.attrs.style">
<svg></svg>
<span t-if="widget.string"><t t-esc="widget.string"/></span>
</div>
</t> </t>
<t t-name="FieldStatus"> <t t-name="FieldStatus">
<ul t-att-class="'oe_form_field_status ' + (widget.options.clickable ? 'oe_form_status_clickable' : 'oe_form_status')" t-att-style="widget.node.attrs.style"/> <ul t-att-class="'oe_form_field_status ' + (widget.options.clickable ? 'oe_form_status_clickable' : 'oe_form_status')" t-att-style="widget.node.attrs.style"/>
@ -1382,7 +1410,7 @@
t-att-autofocus="widget.node.attrs.autofocus" t-att-autofocus="widget.node.attrs.autofocus"
t-att-accesskey="widget.node.attrs.accesskey"> t-att-accesskey="widget.node.attrs.accesskey">
<img t-if="!widget.is_stat_button and widget.node.attrs.icon " t-att-src="_s + widget.node.attrs.icon" width="16" height="16"/> <img t-if="!widget.is_stat_button and widget.node.attrs.icon " t-att-src="_s + widget.node.attrs.icon" width="16" height="16"/>
<div t-if="widget.is_stat_button" class="stat_button_icon"><t t-if="widget.icon" t-raw="widget.icon"/></div> <div t-if="widget.is_stat_button and widget.icon" class="stat_button_icon"><t t-raw="widget.icon"/></div>
<span t-if="widget.string and !widget.is_stat_button"><t t-esc="widget.string"/></span> <span t-if="widget.string and !widget.is_stat_button"><t t-esc="widget.string"/></span>
<div t-if="widget.string and widget.is_stat_button"><t t-esc="widget.string"/></div> <div t-if="widget.string and widget.is_stat_button"><t t-esc="widget.string"/></div>
</button> </button>
@ -1958,5 +1986,6 @@
<a href="javascript:void(0)"><t t-esc="text"/></a> <a href="javascript:void(0)"><t t-esc="text"/></a>
</t> </t>
<t t-name="StatInfo"> <t t-name="StatInfo">
<strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t> <strong><t t-esc="value"/></strong>
<t t-esc="text"/></t>
</templates> </templates>