summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorKunal Chavda (Open ERP) <kch@tinyerp.com>2014-04-18 14:11:42 +0530
committerKunal Chavda (Open ERP) <kch@tinyerp.com>2014-04-18 14:11:42 +0530
commit8b2bc4da90f440c46a27e1297771eb07de26c919 (patch)
treea62b55c2daaacf003f921a5c80544ee5946371d5 /addons/web/static/src
parent1434c34bbacc448ed9358c60b74c2b9c02d5cdb4 (diff)
parent0a6da3662a6f762647517c085214f9abd21500ee (diff)
[MERGE]with latest.
bzr revid: kch@tinyerp.com-20140418084142-d8lm531l8dyk7roo
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/css/base.css324
-rw-r--r--addons/web/static/src/css/base.sass257
-rw-r--r--addons/web/static/src/js/chrome.js214
-rw-r--r--addons/web/static/src/js/core.js12
-rw-r--r--addons/web/static/src/js/data.js72
-rw-r--r--addons/web/static/src/js/data_export.js13
-rw-r--r--addons/web/static/src/js/search.js10
-rw-r--r--addons/web/static/src/js/testing.js73
-rw-r--r--addons/web/static/src/js/view_form.js341
-rw-r--r--addons/web/static/src/js/view_list.js22
-rw-r--r--addons/web/static/src/js/view_list_editable.js11
-rw-r--r--addons/web/static/src/js/views.js30
-rw-r--r--addons/web/static/src/js/watch.js58
-rw-r--r--addons/web/static/src/xml/base.xml87
14 files changed, 843 insertions, 681 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index e43a2fb654e..023c80877ef 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -1,4 +1,4 @@
-@charset "UTF-8";
+@charset "utf-8";
@font-face {
font-family: "mnmliconsRegular";
src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot");
@@ -127,7 +127,7 @@
font-weight: bold;
font-size: inherit;
}
-.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp .oe_button, .openerp input[type='submit'], .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button {
+.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp .oe_button, .openerp input[type='submit'] {
display: inline-block;
border: 1px solid rgba(0, 0, 0, 0.4);
color: #4c4c4c;
@@ -152,7 +152,7 @@
-webkit-font-smoothing: antialiased;
outline: none;
}
-.openerp a.button:hover, .openerp button:hover, .openerp .oe_button:hover, .openerp input[type='submit']:hover, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover {
+.openerp a.button:hover, .openerp button:hover, .openerp .oe_button:hover, .openerp input[type='submit']:hover {
background-color: #ececec;
background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3));
background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3);
@@ -163,7 +163,7 @@
cursor: pointer;
background-position: 0;
}
-.openerp a.button:focus, .openerp button:focus, .openerp .oe_button:focus, .openerp input[type='submit']:focus, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus {
+.openerp a.button:focus, .openerp button:focus, .openerp .oe_button:focus, .openerp input[type='submit']:focus {
border: 1px solid #80bfff;
background-position: 0;
background-color: #ececec;
@@ -177,7 +177,7 @@
-webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset;
box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset;
}
-.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp .oe_button:active, .openerp .oe_button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active {
+.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp .oe_button:active, .openerp .oe_button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active {
background-color: #ececec;
background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6));
background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6);
@@ -212,148 +212,6 @@
.openerp .ui-tabs {
position: static;
}
-.openerp.ui-dialog {
- display: none;
- height: auto !important;
- padding: 6px;
- background-color: rgba(60, 60, 60, 0.7);
- border: 1px solid;
- border-color: #888888 #555555 #444444;
- -moz-border-radius: 8px;
- -webkit-border-radius: 8px;
- border-radius: 8px;
- -moz-box-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
- -webkit-box-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
- box-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
- -webkit-background-clip: padding-box;
- -moz-background-clip: padding-box;
- background-clip: padding-box;
-}
-.openerp.ui-dialog .ui-dialog-content {
- padding: 0;
-}
-.openerp.ui-dialog .ui-dialog-titlebar, .openerp.ui-dialog .ui-dialog-content, .openerp.ui-dialog .ui-dialog-buttonpane {
- padding: 16px;
-}
-.openerp.ui-dialog .ui-dialog-titlebar {
- border-top: none;
- border-left: none;
- border-right: none;
- border-bottom: 1px solid #cacaca;
- -moz-border-radius: 2px 2px 0 0;
- -webkit-border-radius: 2px 2px 0 0;
- border-radius: 2px 2px 0 0;
- background-color: #ededed;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede));
- background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede);
- background-image: -moz-linear-gradient(top, #fcfcfc, #dedede);
- background-image: -ms-linear-gradient(top, #fcfcfc, #dedede);
- background-image: -o-linear-gradient(top, #fcfcfc, #dedede);
- background-image: linear-gradient(to bottom, #fcfcfc, #dedede);
-}
-.openerp.ui-dialog .ui-dialog-titlebar .ui-dialog-title {
- margin: 0;
- padding: 0;
-}
-.openerp.ui-dialog .ui-dialog-content {
- background: white;
- width: auto !important;
-}
-.openerp.ui-dialog .ui-dialog-buttonpane {
- border-top: 1px solid #e0e0e0;
- background: #f5f7f9;
- margin: 0;
- -moz-border-radius: 0 0 2px 2px;
- -webkit-border-radius: 0 0 2px 2px;
- border-radius: 0 0 2px 2px;
-}
-.openerp.ui-dialog .ui-dialog-buttonpane button {
- margin: 0 4px 0 0;
-}
-.openerp.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
- float: left;
-}
-.openerp.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button {
- margin-right: 4px;
-}
-.openerp.ui-dialog .ui-dialog-titlebar-close {
- padding: 0;
-}
-.openerp.ui-dialog .ui-dialog-titlebar-close .ui-icon-closethick {
- display: none;
-}
-.openerp.ui-dialog .ui-dialog-titlebar-close:before {
- content: "×";
- font-size: 18px;
- font-weight: bold;
- line-height: 16px;
- color: black;
- text-shadow: 0 1px 0 white;
- padding: 0;
- cursor: pointer;
- background: transparent;
- border: 0;
-}
-.openerp.ui-dialog .ui-dialog-titlebar-close:before:hover {
- color: black;
- text-decoration: none;
-}
-.openerp.ui-dialog .oe_about {
- background-color: white;
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=);
- -moz-border-radius: 0 0 2px 2px;
- -webkit-border-radius: 0 0 2px 2px;
- border-radius: 0 0 2px 2px;
-}
-.openerp.ui-dialog .oe_about a {
- color: #7c7bad;
-}
-.openerp.ui-dialog .oe_about a:hover {
- text-decoration: underline;
-}
-.openerp.ui-dialog .oe_about a:focus {
- outline: none;
-}
-.openerp.ui-dialog .oe_about .oe_logo {
- margin-left: -6px;
-}
-.openerp.ui-dialog .oe_about .oe_bottom {
- position: absolute;
- top: 50%;
- left: 0;
- right: 0;
- bottom: 0;
- text-shadow: 0 1px 1px #999999;
- background-color: #8a0e0e;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#b41616), to(#600606));
- background-image: -webkit-linear-gradient(top, #b41616, #600606);
- background-image: -moz-linear-gradient(top, #b41616, #600606);
- background-image: -ms-linear-gradient(top, #b41616, #600606);
- background-image: -o-linear-gradient(top, #b41616, #600606);
- background-image: linear-gradient(to bottom, #b41616, #600606);
- color: #eeeeee;
- padding: 0 16px;
- -moz-border-radius: 0 0 2px 2px;
- -webkit-border-radius: 0 0 2px 2px;
- border-radius: 0 0 2px 2px;
-}
-.openerp.ui-dialog .oe_about .oe_bottom a {
- color: #eeeeee;
-}
-.openerp.ui-dialog.oe_act_window .ui-dialog-content {
- padding: 0px;
-}
-.openerp .modal-backdrop {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 1040;
- background-color: black;
- filter: alpha(opacity=30);
- opacity: 0.3;
-}
.openerp .oe_i {
font-family: "mnmliconsRegular" !important;
font-size: 21px;
@@ -368,6 +226,7 @@
.openerp .oe_left {
float: left;
margin-right: 8px;
+ width: 305px;
}
.openerp .oe_right {
float: right;
@@ -456,11 +315,54 @@
background: #ed6f6a;
}
.openerp .oe_button_box {
- width: 270px;
- text-align: right;
+ width: 400px;
+ text-align: left;
+}
+.openerp .oe_button_box .oe_stat_button:hover {
+ background: #7c7bad;
+ color: white;
+}
+.openerp .oe_button_box .oe_stat_button:hover .fa {
+ color: white;
}
-.openerp .oe_button_box button {
- margin: 4px;
+.openerp .oe_button_box .oe_stat_button {
+ font-weight: normal;
+ display: inline-table;
+ width: 33% !important;
+ height: 42px;
+ margin: 0px -1px -1px 0px;
+ padding: 0;
+ color: #666666;
+ border: 1px solid #dddddd;
+ border-radius: 0;
+ box-shadow: none;
+ background: white;
+}
+.openerp .oe_button_box .oe_stat_button > div {
+ display: table-cell;
+ vertical-align: middle;
+ text-align: left;
+ padding: 0;
+ line-height: 120%;
+}
+.openerp .oe_button_box .oe_stat_button .stat_button_icon {
+ color: #7c7bad;
+ font-size: 24px;
+ padding: 0px 3px;
+ width: 37px;
+ 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 {
+ width: 38px;
+ height: 38px;
+ display: inline;
+ vertical-align: middle;
}
.openerp .oe_avatar > img {
max-height: 90px;
@@ -576,7 +478,7 @@
display: inline-block;
height: 12px;
width: 12px;
- vertical-align: bottom;
+ vertical-align: top;
border-radius: 10px;
margin: 1px 0;
}
@@ -602,40 +504,6 @@
font-style: italic;
text-decoration: none;
}
-.openerp.oe_tooltip {
- font-size: 12px;
-}
-.openerp.oe_tooltip .oe_tooltip_string {
- color: #ffdd55;
- font-weight: bold;
- font-size: 13px;
-}
-.openerp.oe_tooltip .oe_tooltip_help {
- white-space: pre-wrap;
-}
-.openerp.oe_tooltip .oe_tooltip_technical {
- padding: 0 0 4px 0;
- margin: 5px 0 0 15px;
-}
-.openerp.oe_tooltip .oe_tooltip_technical li {
- list-style: circle;
-}
-.openerp.oe_tooltip .oe_tooltip_technical_title {
- font-weight: bold;
-}
-.openerp.oe_tooltip .oe_tooltip_close {
- margin: -5px 0 0 2px;
- cursor: default;
- float: right;
- color: white;
-}
-.openerp.oe_tooltip .oe_tooltip_close:hover {
- color: #999999;
- cursor: pointer;
-}
-.openerp.oe_tooltip .oe_tooltip_message {
- max-width: 310px;
-}
.openerp .oe_notebook {
margin: 8px 0;
padding: 0 16px;
@@ -1197,11 +1065,6 @@
margin-left: -6px;
}
.openerp .oe_about .oe_bottom {
- position: absolute;
- top: 50%;
- left: 0;
- right: 0;
- bottom: 0;
text-shadow: 0 1px 1px #999999;
background-color: #8a0e0e;
background-image: -webkit-gradient(linear, left top, left bottom, from(#b41616), to(#600606));
@@ -1248,12 +1111,14 @@
width: 100%;
}
.openerp .oe_view_manager .oe_view_manager_body {
- display: table-row;
height: inherit;
}
.openerp .oe_view_manager .oe_view_manager_view_kanban:not(:empty) {
height: inherit;
}
+.openerp .oe_view_manager[data-view-type=kanban] .oe_view_manager_body {
+ display: table-row;
+}
.openerp .oe_view_manager table.oe_view_manager_header {
border-collapse: separate;
width: 100%;
@@ -2025,7 +1890,7 @@
.openerp .oe_form > :not(.oe_form_nosheet) header {
padding-left: 2px;
}
-.openerp .oe_form > :not(.oe_form_nosheet) header ul {
+.openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical) {
display: inline-block;
float: right;
}
@@ -2339,10 +2204,9 @@
}
.openerp .oe_form .oe_form_embedded_html {
position: relative;
- width: 600px;
- margin-left: 130px;
- margin-top: 32px;
- margin-bottom: 32px;
+ width: 100%;
+ margin: auto;
+ overflow: auto;
text-align: justify;
}
.openerp .oe_form .oe_form_field_html .oe_input_icon {
@@ -2878,7 +2742,7 @@
padding: 3px 6px;
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;
background: transparent;
padding: 0;
@@ -3388,9 +3252,6 @@ body.oe_single_form .oe_single_form_container {
.openerp_ie ul.oe_form_status li.oe_active > .arrow span, .openerp_ie ul.oe_form_status_clickable li.oe_active > .arrow span {
background-color: #729fcf !important;
}
-.openerp_ie .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button {
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#EFEFEF', endColorstr='#D8D8D8');
-}
@media print {
.openerp {
@@ -3437,6 +3298,67 @@ body.oe_single_form .oe_single_form_container {
height: 18px;
}
+.tooltip {
+ padding: 0;
+ margin: 0;
+ font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
+ color: #4c4c4c;
+ font-size: 12px;
+ background: white;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
+ background-color: transparent;
+}
+.tooltip .tooltip-inner {
+ text-align: left !important;
+ max-width: 350px;
+}
+.tooltip .tooltip-inner .oe_tooltip_string {
+ color: #ffdd55;
+ font-weight: bold;
+ font-size: 13px;
+}
+.tooltip .tooltip-inner .oe_tooltip_help {
+ white-space: pre-wrap;
+}
+.tooltip .tooltip-inner .oe_tooltip_technical {
+ padding: 0 0 4px 0;
+ margin: 5px 0 0 15px;
+}
+.tooltip .tooltip-inner .oe_tooltip_technical li {
+ list-style: circle;
+}
+.tooltip .tooltip-inner .oe_tooltip_technical_title {
+ font-weight: bold;
+}
+.tooltip .tooltip-inner .oe_tooltip_close {
+ margin: -5px 0 0 2px;
+ cursor: default;
+ float: right;
+ color: white;
+}
+.tooltip .tooltip-inner .oe_tooltip_close:hover {
+ color: #999999;
+ cursor: pointer;
+}
+.tooltip .tooltip-inner .oe_tooltip_message {
+ max-width: 310px;
+}
+.modal .modal-header button.close {
+ border: none;
+ background: none;
+ padding: 1px;
+ height: 18px;
+ font-size: 20px;
+}
+.modal .modal-footer {
+ text-align: left;
+}
+.modal .oe_act_window.modal-body{
+ padding: 0;
+}
+.modal .oe_button{
+ margin: 0 4px 0 0;
+}
input[type="radio"], input[type="checkbox"] {
margin-right: 4px;
margin-left: 4px;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index b19db9e4ebd..84758c062cb 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -218,7 +218,7 @@ $sheet-padding: 16px
font-size: inherit
// }}}
// Button style {{{
- a.button:link, a.button:visited, button, .oe_button, input[type='submit'], .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button
+ a.button:link, a.button:visited, button, .oe_button, input[type='submit']
display: inline-block
border: 1px solid rgba(0,0,0,0.4)
color: #4c4c4c
@@ -233,18 +233,18 @@ $sheet-padding: 16px
-webkit-font-smoothing: antialiased
outline: none
- a.button:hover, button:hover,.oe_button:hover, input[type='submit']:hover, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover
+ a.button:hover, 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, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus
+ a.button:focus, 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, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active
+ a.button:active, a.button.active, 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)
@@ -272,105 +272,6 @@ $sheet-padding: 16px
.ui-tabs
position: static
- // Modal box
- &.ui-dialog
- display: none
- height: auto !important
- padding: 6px
- //overflow: hidden
- background-color: rgba(60,60,60,0.7)
- border: 1px solid
- border-color: #888 #555 #444
- //overflow: hidden
- @include radius(8px)
- @include box-shadow(0 1px 12px rgba(0, 0, 0, 0.6))
- @include background-clip()
- .ui-dialog-content
- padding: 0
- .ui-dialog-titlebar, .ui-dialog-content, .ui-dialog-buttonpane
- padding: 16px
- .ui-dialog-titlebar
- border-top: none
- border-left: none
- border-right: none
- border-bottom: 1px solid #cacaca
- @include radius(2px 2px 0 0)
- @include vertical-gradient(#FCFCFC, #DEDEDE)
- .ui-dialog-title
- margin: 0
- padding: 0
- .ui-dialog-content
- background: white
- width: auto !important
- .ui-dialog-buttonpane
- border-top: 1px solid #e0e0e0
- background: #f5f7f9
- margin: 0
- @include radius(0 0 2px 2px)
- button
- margin: 0 4px 0 0
- .ui-dialog-buttonset
- float: left
- .ui-button
- margin-right: 4px
- .ui-dialog-titlebar-close
- padding: 0
- .ui-icon-closethick
- display: none
- &:before
- content: "×"
- font-size: 18px
- font-weight: bold
- line-height: 16px
- color: black
- text-shadow: 0 1px 0 white
- padding: 0
- cursor: pointer
- background: transparent
- border: 0
- &:hover
- color: black
- text-decoration: none
- .oe_about
- background-color: white
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=)
- @include radius(0 0 2px 2px)
- a
- color: $link-color
- &:hover
- text-decoration: underline
- &:focus
- outline: none
- .oe_logo
- margin-left: -6px
- .oe_bottom
- position: absolute
- top: 50%
- left: 0
- right: 0
- bottom: 0
- text-shadow: 0 1px 1px #999999
- @include vertical-gradient(#b41616, #600606)
- color: #eee
- padding: 0 16px
- @include radius(0 0 2px 2px)
- a
- color: #eee
-
- &.ui-dialog.oe_act_window
- .ui-dialog-content
- padding: 0px
-
- .modal-backdrop
- position: fixed
- top: 0
- right: 0
- bottom: 0
- left: 0
- z-index: 1040
- background-color: black
- @include opacity(.3)
-
// }}}
// Generic classes {{{
.oe_i
@@ -385,6 +286,7 @@ $sheet-padding: 16px
.oe_left
float: left
margin-right: 8px
+ width: 305px
.oe_right
float: right
margin-left: 8px
@@ -429,10 +331,46 @@ $sheet-padding: 16px
&:hover
background: #ED6F6A
.oe_button_box
- width: 270px
- text-align: right
- button
- margin: 4px
+ width: 400px
+ text-align: left
+ .oe_stat_button:hover
+ background: #7c7bad
+ color: white
+ .fa
+ color: white
+ .oe_stat_button
+ font-weight: normal
+ display: inline-table
+ width: 33% !important
+ height: 42px
+ margin: 0px -1px -1px 0px
+ padding: 0
+ color: #666
+ border: 1px solid #dddddd
+ border-radius: 0
+ box-shadow: none
+ background: white
+ > div
+ display: table-cell
+ vertical-align: middle
+ text-align: left
+ padding: 0
+ line-height: 120%
+ .stat_button_icon
+ color: #7C7BAD
+ font-size: 24px
+ padding: 0px 3px
+ width: 37px
+ text-align: center
+ .oe_form_field_percent_pie
+ width: 42px
+ .oe_form_field_bar_chart
+ width: 42px
+ svg
+ width: 38px
+ height: 38px
+ display: inline
+ vertical-align: middle
.oe_avatar
> img
max-height: 90px
@@ -514,7 +452,7 @@ $sheet-padding: 16px
display: inline-block
height: 12px
width: 12px
- vertical-align: bottom
+ vertical-align: top
border-radius: 10px
margin: 1px 0
&.oe_form_invalid
@@ -534,33 +472,6 @@ $sheet-padding: 16px
text-decoration: none
margin-bottom: 1px
// }}}
- // Tooltips {{{
- &.oe_tooltip
- font-size: 12px
- .oe_tooltip_string
- color: #FD5
- font-weight: bold
- font-size: 13px
- .oe_tooltip_help
- white-space: pre-wrap
- .oe_tooltip_technical
- padding: 0 0 4px 0
- margin: 5px 0 0 15px
- li
- list-style: circle
- .oe_tooltip_technical_title
- font-weight: bold
- .oe_tooltip_close
- margin: -5px 0 0 2px
- cursor: default
- float: right
- color: white
- &:hover
- color: #999
- cursor: pointer
- .oe_tooltip_message
- max-width: 310px
- // }}}
// Notebook {{{
.oe_notebook
margin: 8px 0
@@ -999,11 +910,6 @@ $sheet-padding: 16px
.oe_logo
margin-left: -6px
.oe_bottom
- position: absolute
- top: 50%
- left: 0
- right: 0
- bottom: 0
text-shadow: 0 1px 1px #999999
@include vertical-gradient(#b41616, #600606)
color: #eee
@@ -1037,10 +943,12 @@ $sheet-padding: 16px
height: inherit
width: 100%
.oe_view_manager_body
- display: table-row
height: inherit
.oe_view_manager_view_kanban:not(:empty)
height: inherit
+ &[data-view-type=kanban]
+ .oe_view_manager_body
+ display: table-row
table.oe_view_manager_header
border-collapse: separate
@@ -1642,7 +1550,7 @@ $sheet-padding: 16px
// FormView.header {{{
.oe_form > :not(.oe_form_nosheet) header
padding-left: 2px
- ul
+ ul:not(.oe_tooltip_technical)
display: inline-block
float: right
.oe_button
@@ -1893,10 +1801,9 @@ $sheet-padding: 16px
overflow: hidden
.oe_form_embedded_html
position: relative
- width: 600px
- margin-left: 130px
- margin-top: 32px
- margin-bottom: 32px
+ width: 100%
+ margin: auto
+ overflow: auto
text-align: justify
.oe_form_field_html .oe_input_icon
float: right
@@ -2313,7 +2220,7 @@ $sheet-padding: 16px
padding: 3px 6px
white-space: pre-line
> td, > th
- > button
+ > button.btn_img
border: none
background: transparent
padding: 0
@@ -2732,9 +2639,6 @@ body.oe_single_form
> .arrow span
background-color: #729fcf !important
- // jquery ui for ie
- .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#EFEFEF', endColorstr='#D8D8D8')
// }}}
// @media print {{{
@@ -2772,6 +2676,44 @@ body.oe_single_form
overflow: hidden !important
// }}}
+// End of customize
+
+// Customize bootstrap3 for tooltip
+.tooltip
+ padding: 0
+ margin: 0
+ font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif
+ color: #4c4c4c
+ font-size: 12px
+ background: white
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5)
+ background-color: transparent
+ .tooltip-inner
+ text-align: left !important
+ max-width: 350px
+ .oe_tooltip_string
+ color: #FD5
+ font-weight: bold
+ font-size: 13px
+ .oe_tooltip_help
+ white-space: pre-wrap
+ .oe_tooltip_technical
+ padding: 0 0 4px 0
+ margin: 5px 0 0 15px
+ li
+ list-style: circle
+ .oe_tooltip_technical_title
+ font-weight: bold
+ .oe_tooltip_close
+ margin: -5px 0 0 2px
+ cursor: default
+ float: right
+ color: white
+ &:hover
+ color: #999
+ cursor: pointer
+ .oe_tooltip_message
+ max-width: 310px
// Hack for ui icon {{{
.ui-icon
@@ -2779,6 +2721,21 @@ body.oe_single_form
height: 18px
// End hack}}}
+// Customized modal according bootstrap3
+.modal
+ .modal-header button.close
+ border: none
+ background: none
+ padding: 1px
+ height: 18px
+ font-size: 20px
+ .modal-footer
+ text-align: left
+ .oe_button
+ margin: 0 4px 0 0
+ .oe_act_window.modal-body
+ padding: 0
+
input[type="radio"], input[type="checkbox"]
margin-right: 4px
margin-left: 4px
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 461bfefac1a..3c1f55df981 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -57,16 +57,6 @@ instance.web.action_warn = function(element, action) {
instance.web.client_actions.add("action_warn", "instance.web.action_warn");
/**
- * The very minimal function everything should call to create a dialog
- * in OpenERP Web Client.
- */
-instance.web.dialog = function(element) {
- var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
- result.dialog("widget").openerpClass();
- return result;
-};
-
-/**
A useful class to handle dialogs.
Attributes:
@@ -81,6 +71,8 @@ instance.web.Dialog = instance.web.Widget.extend({
@param {Widget} parent
@param {dictionary} options A dictionary that will be forwarded to jQueryUI Dialog. Additionaly, that
dictionary can contain the following keys:
+ - size: one of the following: 'large', 'medium', 'small'
+ - dialogClass: class to add to the body of dialog
- buttons: Deprecated. The buttons key is not propagated to jQueryUI Dialog. It must be a dictionary (key = button
label, value = click handler) or a list of dictionaries (each element in the dictionary is send to the
corresponding method of a jQuery element targeting the <button> tag). It is deprecated because all dialogs
@@ -94,60 +86,15 @@ instance.web.Dialog = instance.web.Widget.extend({
this._super(parent);
this.content_to_set = content;
this.dialog_options = {
- modal: true,
destroy_on_close: true,
- width: 900,
- min_width: 0,
- max_width: '95%',
- height: 'auto',
- min_height: 0,
- max_height: $(window.top).height() - 200,
- autoOpen: false,
- position: [false, 40],
+ size: 'large', //'medium', 'small'
buttons: null,
- beforeClose: function () {
- self.trigger("closing");
- },
- resizeStop: function() {
- self.trigger("resized");
- },
};
if (options) {
_.extend(this.dialog_options, options);
}
this.on("closing", this, this._closing);
- this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"><span class="oe_dialog_custom_buttons"/></div>');
- },
- _get_options: function() {
- var self = this;
- var o = _.extend({}, this.dialog_options);
- var sizes = {
- width: $(window.top).width(),
- height: $(window.top).height(),
- };
- _.each(sizes, function(available_size, unit) {
- o[unit] = self._get_size(o[unit], available_size);
- o['min_' + unit] = self._get_size(o['min_' + unit] || 0, available_size);
- o['max_' + unit] = self._get_size(o['max_' + unit] || 0, available_size);
- if (o[unit] !== 'auto' && o['min_' + unit] && o[unit] < o['min_' + unit]) {
- o[unit] = o['min_' + unit];
- }
- if (o[unit] !== 'auto' && o['max_' + unit] && o[unit] > o['max_' + unit]) {
- o[unit] = o['max_' + unit];
- }
- });
- o.title = o.title || this.dialog_title;
- return o;
- },
- _get_size: function(val, available_size) {
- val = val.toString();
- if (val === 'auto') {
- return val;
- } else if (val.slice(-1) === "%") {
- return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
- } else {
- return parseInt(val, 10);
- }
+ this.$buttons = $('<div class="modal-footer"><span class="oe_dialog_custom_buttons"/></div>');
},
renderElement: function() {
if (this.content_to_set) {
@@ -165,8 +112,7 @@ instance.web.Dialog = instance.web.Widget.extend({
if (!this.dialog_inited) {
this.init_dialog();
}
- this.$el.dialog('open');
- this.$el.dialog("widget").append(this.$buttons);
+ this.$buttons.insertAfter(this.$dialog_box.find(".modal-body"));
return this;
},
_add_buttons: function(buttons) {
@@ -191,16 +137,39 @@ instance.web.Dialog = instance.web.Widget.extend({
@return The result returned by start().
*/
init_dialog: function() {
- var options = this._get_options();
+ var self = this;
+ var options = _.extend({}, this.dialog_options);
+ options.title = options.title || this.dialog_title;
if (options.buttons) {
this._add_buttons(options.buttons);
delete(options.buttons);
}
this.renderElement();
- instance.web.dialog(this.$el, options);
- if (options.height === 'auto' && options.max_height) {
- this.$el.css({ 'max-height': options.max_height, 'overflow-y': 'auto' });
+
+ this.$dialog_box = $(QWeb.render('Dialog', options)).appendTo("body");
+ this.$el.modal({
+ 'backdrop': false,
+ 'keyboard': true,
+ });
+ if (options.size !== 'large'){
+ var dialog_class_size = this.$dialog_box.find('.modal-lg').removeClass('modal-lg')
+ if (options.size === 'small'){
+ dialog_class_size.addClass('modal-sm');
+ }
}
+
+ this.$el.appendTo(this.$dialog_box.find(".modal-body"));
+ var $dialog_content = this.$dialog_box.find('.modal-content');
+ if (options.dialogClass){
+ $dialog_content.find(".modal-body").addClass(options.dialogClass);
+ }
+ $dialog_content.openerpClass();
+
+ this.$dialog_box.on('hidden.bs.modal', this, function(){
+ self.close();
+ });
+ this.$dialog_box.modal('show');
+
this.dialog_inited = true;
var res = this.start();
return res;
@@ -208,9 +177,12 @@ instance.web.Dialog = instance.web.Widget.extend({
/**
Closes the popup, if destroy_on_close was passed to the constructor, it is also destroyed.
*/
- close: function() {
- if (this.dialog_inited && this.$el.is(":data(dialog)")) {
- this.$el.dialog('close');
+ close: function(reason) {
+ if (this.dialog_inited) {
+ this.trigger("closing", reason);
+ if (this.$el.is(":data(bs.modal)")) { // may have been destroyed by closing signal
+ this.$el.parents('.modal').modal('hide');
+ }
}
},
_closing: function() {
@@ -225,18 +197,25 @@ instance.web.Dialog = instance.web.Widget.extend({
/**
Destroys the popup, also closes it.
*/
- destroy: function () {
+ destroy: function (reason) {
this.$buttons.remove();
+ var self = this;
_.each(this.getChildren(), function(el) {
el.destroy();
});
if (! this.__tmp_dialog_closing) {
this.__tmp_dialog_destroying = true;
- this.close();
+ this.close(reason);
this.__tmp_dialog_destroying = undefined;
}
- if (this.dialog_inited && !this.isDestroyed() && this.$el.is(":data(dialog)")) {
- this.$el.dialog('destroy');
+ if (this.dialog_inited && !this.isDestroyed() && this.$el.is(":data(bs.modal)")) {
+ //we need this to put the instruction to remove modal from DOM at the end
+ //of the queue, otherwise it might already have been removed before the modal-backdrop
+ //is removed when pressing escape key
+ var $parent = this.$el.parents('.modal');
+ setTimeout(function () {
+ $parent.remove();
+ },0);
}
this._super();
}
@@ -256,7 +235,7 @@ instance.web.CrashManager = instance.web.Class.extend({
new (handler)(this, error).display();
return;
}
- if (error.data.name === "openerp.http.SessionExpiredException") {
+ if (error.data.name === "openerp.http.SessionExpiredException" || error.data.name === "werkzeug.exceptions.Forbidden") {
this.show_warning({type: "Session Expired", data: { message: _t("Your OpenERP session expired. Please refresh the current web page.") }});
return;
}
@@ -274,12 +253,13 @@ instance.web.CrashManager = instance.web.Class.extend({
if (error.data.exception_type === "except_osv") {
error = _.extend({}, error, {data: _.extend({}, error.data, {message: error.data.arguments[0] + "\n\n" + error.data.arguments[1]})});
}
- instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
+ new instance.web.Dialog(this, {
+ size: 'medium',
title: "OpenERP " + (_.str.capitalize(error.type) || "Warning"),
buttons: [
- {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
- ]
- });
+ {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
+ ],
+ }, $('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>')).open();
},
show_error: function(error) {
if (!this.active) {
@@ -287,17 +267,12 @@ instance.web.CrashManager = instance.web.Class.extend({
}
var buttons = {};
buttons[_t("Ok")] = function() {
- $(this).dialog("close");
+ this.parents('.modal').modal('hide');
};
- var dialog = new instance.web.Dialog(this, {
+ new instance.web.Dialog(this, {
title: "OpenERP " + _.str.capitalize(error.type),
- width: '80%',
- height: '50%',
- min_width: '800px',
- min_height: '600px',
buttons: buttons
- }).open();
- dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error}));
+ }, QWeb.render('CrashManager.error', {session: instance.session, error: error})).open();
},
show_message: function(exception) {
this.show_error({
@@ -344,16 +319,17 @@ instance.web.RedirectWarningHandler = instance.web.Dialog.extend(instance.web.Ex
error = this.error;
error.data.message = error.data.arguments[0];
- instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
+ new instance.web.Dialog(this, {
+ size: 'medium',
title: "OpenERP " + (_.str.capitalize(error.type) || "Warning"),
buttons: [
- {text: _t("Ok"), click: function() { $(this).dialog("close"); }},
+ {text: _t("Ok"), click: function() { this.$el.parents('.modal').modal('hide'); }},
{text: error.data.arguments[2], click: function() {
window.location.href='#action='+error.data.arguments[1];
- $(this).dialog("close");
+ this.$el.parents('.modal').modal('hide');
}}
- ]
- });
+ ],
+ }, QWeb.render('CrashManager.warning', {error: error})).open();
this.destroy();
}
});
@@ -500,20 +476,20 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
* @param {String} error.error message of the error dialog
*/
display_error: function (error) {
- return instance.web.dialog($('<div>'), {
- modal: true,
+ return new instance.web.Dialog(this, {
+ size: 'medium',
title: error.title,
buttons: [
- {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+ {text: _t("Ok"), click: function() { this.$el.parents('.modal').modal('hide'); }}
]
- }).html(error.error);
+ }, $('<div>').html(error.error)).open();
},
do_create: function(form) {
var self = this;
var fields = $(form).serializeArray();
self.rpc("/web/database/create", {'fields': fields}).done(function(result) {
if (result) {
- instance.web.redirect('/web')
+ instance.web.redirect('/web');
} else {
alert("Failed to create database");
}
@@ -733,13 +709,13 @@ instance.web.ChangePassword = instance.web.Widget.extend({
});
},
display_error: function (error) {
- return instance.web.dialog($('<div>'), {
- modal: true,
+ return new instance.web.Dialog(this, {
+ size: 'medium',
title: error.title,
buttons: [
- {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+ {text: _t("Ok"), click: function() { this.$el.parents('.modal').modal('hide'); }}
]
- }).html(error.error);
+ }, $('<div>').html(error.error)).open();
},
});
instance.web.client_actions.add("change_password", "instance.web.ChangePassword");
@@ -1054,8 +1030,11 @@ instance.web.UserMenu = instance.web.Widget.extend({
e.preventDefault();
window.location = $.param.querystring( window.location.href, 'debug');
});
- instance.web.dialog($help, {autoOpen: true,
- modal: true, width: 507, height: 290, resizable: false, title: _t("About")});
+ new instance.web.Dialog(this, {
+ size: 'medium',
+ dialogClass: 'oe_act_window',
+ title: _t("About"),
+ }, $help).open();
});
},
});
@@ -1099,8 +1078,8 @@ instance.web.Client = instance.web.Widget.extend({
},
bind_events: function() {
var self = this;
- this.$el.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() {
- $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter');
+ this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function() {
+ $(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
});
this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
ev.preventDefault();
@@ -1124,7 +1103,7 @@ instance.web.Client = instance.web.Widget.extend({
}, 0);
});
instance.web.bus.on('click', this, function(ev) {
- $.fn.tipsy.clear();
+ $.fn.tooltip('destroy');
if (!$(ev.target).is('input[type=file]')) {
self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened');
}
@@ -1264,9 +1243,9 @@ instance.web.WebClient = instance.web.Client.extend({
};
self.action_manager.do_action(result);
var form = self.action_manager.dialog_widget.views.form.controller;
- form.on("on_button_cancel", self.action_manager.dialog, self.action_manager.dialog.close);
+ form.on("on_button_cancel", self.action_manager, self.action_manager.dialog_stop);
form.on('record_saved', self, function() {
- self.action_manager.dialog.close();
+ self.action_manager.dialog_stop();
self.update_logo();
});
});
@@ -1337,12 +1316,13 @@ instance.web.WebClient = instance.web.Client.extend({
if (_.isEmpty(state) || state.action == "login") {
self.menu.has_been_loaded.done(function() {
new instance.web.Model("res.users").call("read", [self.session.uid, ["action_id"]]).done(function(data) {
- var first_menu_id = self.menu.$el.find("a:first").data("menu");
- if(first_menu_id)
- self.menu.menu_click(first_menu_id);
-
if(data.action_id) {
self.action_manager.do_action(data.action_id[0]);
+ self.menu.open_action(data.action_id[0]);
+ } else {
+ var first_menu_id = self.menu.$el.find("a:first").data("menu");
+ if(first_menu_id)
+ self.menu.menu_click(first_menu_id);
}
});
});
@@ -1488,24 +1468,6 @@ instance.web.embed = function (origin, dbname, login, key, action, options) {
client.insertAfter(currentScript);
};
-openerp.web.LoginForm = openerp.web.Widget.extend({
- init: function ($form) {
- this._super(/* no parent */);
- this.setElement($form);
- this.$el.on('submit', this.on_submit);
- this.start();
- },
- start: function () {
- if (location.hash) {
- this.$el.attr('action', this.$el.attr('action') + location.hash);
- }
- return this._super();
- },
- on_submit: function () {
- return true;
- },
-});
-
})();
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js
index b92ca1d3ba5..5339fa80615 100644
--- a/addons/web/static/src/js/core.js
+++ b/addons/web/static/src/js/core.js
@@ -271,7 +271,12 @@ instance.web.Session.include( /** @lends instance.web.Session# */{
for(var i=0; i<cookies.length; ++i) {
var cookie = cookies[i].replace(/^\s*/, '');
if(cookie.indexOf(nameEQ) === 0) {
- return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length)));
+ try {
+ return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length)));
+ } catch(err) {
+ // wrong cookie, delete it
+ this.set_cookie(name, '', -1);
+ }
}
}
return null;
@@ -783,6 +788,11 @@ instance.web.unblockUI = function() {
return $.unblockUI.apply($, arguments);
};
+
+/* Bootstrap defaults overwrite */
+$.fn.tooltip.Constructor.DEFAULTS.placement = 'bottom';
+$.fn.tooltip.Constructor.DEFAULTS.html = true;
+
/**
* Registry for all the client actions key: tag value: widget
*/
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index cc595c30688..6819634655d 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -27,6 +27,7 @@ instance.web.Query = instance.web.Class.extend({
this._fields = fields;
this._filter = [];
this._context = {};
+ this._lazy = true;
this._limit = false;
this._offset = 0;
this._order_by = [];
@@ -36,6 +37,7 @@ instance.web.Query = instance.web.Class.extend({
var q = new instance.web.Query(this._model, this._fields);
q._context = this._context;
q._filter = this._filter;
+ q._lazy = this._lazy;
q._limit = this._limit;
q._offset = this._offset;
q._order_by = this._order_by;
@@ -51,6 +53,7 @@ instance.web.Query = instance.web.Class.extend({
q._context = new instance.web.CompoundContext(
q._context, to_set.context);
break;
+ case 'lazy':
case 'limit':
case 'offset':
case 'order_by':
@@ -140,6 +143,7 @@ instance.web.Query = instance.web.Class.extend({
domain: this._model.domain(this._filter),
context: ctx,
offset: this._offset,
+ lazy: this._lazy,
limit: this._limit,
orderby: instance.web.serialize_sort(this._order_by) || false
}).then(function (results) {
@@ -148,8 +152,9 @@ instance.web.Query = instance.web.Class.extend({
result.__context = result.__context || {};
result.__context.group_by = result.__context.group_by || [];
_.defaults(result.__context, ctx);
+ var grouping_fields = self._lazy ? [grouping[0]] : grouping;
return new instance.web.QueryGroup(
- self._model.name, grouping[0], result);
+ self._model.name, grouping_fields, result);
});
});
},
@@ -176,6 +181,18 @@ instance.web.Query = instance.web.Class.extend({
return this.clone({filter: domain});
},
/**
+ * Creates a new query with the provided parameter lazy replacing the current
+ * query's own.
+ *
+ * @param {Boolean} lazy indicates if the read_group should return only the
+ * first level of groupby records, or should return the records grouped by
+ * all levels at once (so, it makes only 1 db request).
+ * @returns {openerp.web.Query}
+ */
+ lazy: function (lazy) {
+ return this.clone({lazy: lazy});
+ },
+ /**
* Creates a new query with the provided limit replacing the current
* query's own limit
*
@@ -213,7 +230,7 @@ instance.web.Query = instance.web.Class.extend({
});
instance.web.QueryGroup = instance.web.Class.extend({
- init: function (model, grouping_field, read_group_group) {
+ init: function (model, grouping_fields, read_group_group) {
// In cases where group_by_no_leaf and no group_by, the result of
// read_group has aggregate fields but no __context or __domain.
// Create default (empty) values for those so that things don't break
@@ -221,11 +238,12 @@ instance.web.QueryGroup = instance.web.Class.extend({
{__context: {group_by: []}, __domain: []},
read_group_group);
+ var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count';
var aggregates = {};
_(fixed_group).each(function (value, key) {
if (key.indexOf('__') === 0
- || key === grouping_field
- || key === grouping_field + '_count') {
+ || _.contains(grouping_fields, key)
+ || (key === count_key)) {
return;
}
aggregates[key] = value || 0;
@@ -234,16 +252,21 @@ instance.web.QueryGroup = instance.web.Class.extend({
this.model = new instance.web.Model(
model, fixed_group.__context, fixed_group.__domain);
- var raw_field = grouping_field && grouping_field.split(':')[0];
- var group_size = fixed_group[raw_field + '_count'] || fixed_group.__count || 0;
+ var group_size = fixed_group[count_key] || fixed_group.__count || 0;
var leaf_group = fixed_group.__context.group_by.length === 0;
+ var value = (grouping_fields.length === 1)
+ ? fixed_group[grouping_fields[0]]
+ : _.map(grouping_fields, function (field) { return fixed_group[field]; });
+ var grouped_on = (grouping_fields.length === 1)
+ ? grouping_fields[0]
+ : grouping_fields;
this.attributes = {
folded: !!(fixed_group.__fold),
- grouped_on: grouping_field,
+ grouped_on: grouped_on,
// if terminal group (or no group) and group_by_no_leaf => use group.__count
length: group_size,
- value: fixed_group[raw_field],
+ value: value,
// A group is open-able if it's not a leaf in group_by_no_leaf mode
has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']),
@@ -448,7 +471,8 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
* Read records.
*
* @param {Array} ids identifiers of the records to read
- * @param {Array} fields fields to read and return, by default all fields are returned
+ * @param {Array} [fields] fields to read and return, by default all fields are returned
+ * @param {Object} [options]
* @returns {$.Deferred}
*/
read_ids: function (ids, fields, options) {
@@ -456,10 +480,28 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
return $.Deferred().resolve([]);
options = options || {};
- // TODO: reorder results to match ids list
- return this._model.call('read',
- [ids, fields || false],
- {context: this.get_context(options.context)});
+ var method = 'read';
+ var ids_arg = ids;
+ var context = this.get_context(options.context);
+ if (options.check_access_rule === true){
+ method = 'search_read';
+ ids_arg = [['id', 'in', ids]];
+ context = new instance.web.CompoundContext(context, {active_test: false});
+ }
+ return this._model.call(method,
+ [ids_arg, fields || false],
+ {context: context})
+ .then(function (records) {
+ if (records.length <= 1) { return records; }
+ var indexes = {};
+ for (var i = 0; i < ids.length; i++) {
+ indexes[ids[i]] = i;
+ }
+ records.sort(function (a, b) {
+ return indexes[a.id] - indexes[b.id];
+ });
+ return records;
+ });
},
/**
* Read a slice of the records represented by this DataSet, based on its
@@ -904,6 +946,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
sign = -1;
field = field.slice(1);
}
+ //m2o should be searched based on value[1] not based whole value(i.e. [id, value])
+ if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){
+ return sign * compare(a[field][1], b[field][1]);
+ }
return sign * compare(a[field], b[field]);
}, 0);
});
diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js
index 2725e18ea6e..2de933f45e8 100644
--- a/addons/web/static/src/js/data_export.js
+++ b/addons/web/static/src/js/data_export.js
@@ -46,7 +46,6 @@ instance.web.DataExport = instance.web.Dialog.extend({
start: function() {
var self = this;
this._super.apply(this, arguments);
- self.$el.removeClass('ui-dialog-content ui-widget-content');
var got_fields = new $.Deferred();
this.$el.find('#import_compat').change(function() {
@@ -119,6 +118,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
if (select_exp.val()) {
self.exports.unlink([parseInt(select_exp.val(), 10)]);
select_exp.remove();
+ self.$el.find("#fields_list option").remove();
if (self.$el.find('#saved_export_list option').length <= 1) {
self.$el.find('#ExistsExportList').hide();
}
@@ -166,18 +166,16 @@ instance.web.DataExport = instance.web.Dialog.extend({
export_fields: _(fields).map(function (field) {
return [0, 0, {name: field}];
})
- }, function (export_list_id) {
- if (!export_list_id.result) {
+ }).then(function (export_list_id) {
+ if (!export_list_id) {
return;
}
- self.$el.find("#saved_export_list").append(
- new Option(value, export_list_id.result));
- if (self.$el.find("#saved_export_list").is(":hidden")) {
+ if (!self.$el.find("#saved_export_list").length || self.$el.find("#saved_export_list").is(":hidden")) {
self.show_exports_list();
}
+ self.$el.find("#saved_export_list").append( new Option(value, export_list_id) );
});
this.on_show_save_list();
- this.$el.find("#fields_list option").remove();
},
on_click: function(id, record) {
var self = this;
@@ -414,7 +412,6 @@ instance.web.DataExport = instance.web.Dialog.extend({
});
},
close: function() {
- this.$el.remove();
this._super();
}
});
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index b667d135bdd..04bb1a61d99 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -355,7 +355,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
}
},
'autocompleteopen': function () {
- this.$el.autocomplete('widget').css('z-index', 1004);
+ this.$el.autocomplete('widget').css('z-index', 9999);
},
},
/**
@@ -677,6 +677,11 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
* @returns instance.web.search.Field
*/
make_field: function (item, field, parent) {
+ // M2O combined with selection widget is pointless and broken in search views,
+ // but has been used in the past for unsupported hacks -> ignore it
+ if (field.type === "many2one" && item.attrs.widget === "selection"){
+ item.attrs.widget = undefined;
+ }
var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
if(obj) {
return new (obj) (item, field, parent || this);
@@ -1555,9 +1560,6 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
this.model = new instance.web.Model(this.attrs.relation);
},
complete: function (needle) {
- if (this.attrs.operator || this.attrs.filter_domain) {
- return this._super(needle);
- }
var self = this;
// FIXME: "concurrent" searches (multiple requests, mis-ordered responses)
var context = instance.web.pyeval.eval(
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
index c4f5aeebd60..1f1c718ad5f 100644
--- a/addons/web/static/src/js/testing.js
+++ b/addons/web/static/src/js/testing.js
@@ -196,7 +196,6 @@ openerp.testing = {};
});
};
- var db = window['oe_db_info'];
testing.section = function (name, options, body) {
if (_.isFunction(options)) {
body = options;
@@ -235,40 +234,6 @@ openerp.testing = {};
.push(env._oe.setup, env._oe.teardown)
.push(options.setup, options.teardown);
var opts = _.defaults({}, options, env._oe);
- // FIXME: if this test is ignored, will still query
- if (opts.rpc === 'rpc' && !db) {
- QUnit.config.autostart = false;
- db = {
- source: null,
- supadmin: null,
- password: null
- };
- var $msg = $('<form style="margin: 0 1em 1em;">')
- .append('<h3>A test needs to clone a database</h3>')
- .append('<h4>Please provide the source clone information</h4>')
- .append(' Source DB: ').append('<input name="source">').append('<br>')
- .append(' DB Password: ').append('<input name="supadmin">').append('<br>')
- .append('Admin Password: ').append('<input name="password">').append('<br>')
- .append('<input type="submit" value="OK"/>')
- .submit(function (e) {
- e.preventDefault();
- e.stopPropagation();
- db.source = $msg.find('input[name=source]').val();
- db.supadmin = $msg.find('input[name=supadmin]').val();
- db.password = $msg.find('input[name=password]').val();
- QUnit.start();
- $.unblockUI();
- });
- $.blockUI({
- message: $msg,
- css: {
- fontFamily: 'monospace',
- textAlign: 'left',
- whiteSpace: 'pre-wrap',
- cursor: 'default'
- }
- });
- }
QUnit.test(name, function () {
var instance = openerp;
@@ -302,44 +267,6 @@ openerp.testing = {};
instance.session.responses[spec] = handler;
};
break;
- case 'rpc':
- async = true;
- (function () {
- // Bunch of random base36 characters
- var dbname = 'test_' + Math.random().toString(36).slice(2);
- // Add db setup/teardown at the start of the stack
- case_stack = case_stack.unshift(function (instance) {
- // FIXME hack: don't want the session to go through shitty loading process of everything
- instance.session.session_init = testing.noop;
- instance.session.load_modules = testing.noop;
- instance.session.session_bind();
- return instance.session.rpc('/web/database/duplicate', {
- fields: [
- {name: 'super_admin_pwd', value: db.supadmin},
- {name: 'db_original_name', value: db.source},
- {name: 'db_name', value: dbname}
- ]
- }).then(function (result) {
- if (result.error) {
- return $.Deferred().reject(result.error).promise();
- }
- return instance.session.session_authenticate(
- dbname, 'admin', db.password, true);
- });
- }, function (instance) {
- return instance.session.rpc('/web/database/drop', {
- fields: [
- {name: 'drop_pwd', value: db.supadmin},
- {name: 'drop_db', value: dbname}
- ]
- }).then(function (result) {
- if (result.error) {
- return $.Deferred().reject(result.error).promise();
- }
- return result;
- });
- });
- })();
}
// Always execute tests asynchronously
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 9f086a206dc..7453286dc86 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -586,13 +586,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this._internal_set_values(result.value, processed);
}
if (!_.isEmpty(result.warning)) {
- instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), {
+ new instance.web.Dialog(this, {
+ size: 'medium',
title:result.warning.title,
- modal: true,
buttons: [
- {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+ {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
]
- });
+ }, QWeb.render("CrashManager.warning", result.warning)).open();
}
return $.Deferred().resolve();
@@ -631,6 +631,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
});
}
return $.when();
+ }).fail(function() {
+ self.save_list.pop();
+ return $.when();
});
}
return iterate();
@@ -967,9 +970,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
context: {
'bin_size': true,
'future_display_name': true
- }
+ },
+ check_access_rule: true
}).then(function(r) {
self.trigger('load_record', r);
+ }).fail(function (){
+ self.do_action('history_back');
});
}
});
@@ -1077,7 +1083,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
};
})
.value();
-
var d = new instance.web.Dialog(this, {
title: _t("Set Default"),
args: {
@@ -1277,6 +1282,9 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
var defs = [];
_.each(this.to_replace, function(el) {
defs.push(el[0].replace(el[1]));
+ if (el[1].children().length) {
+ el[0].$el.append(el[1].children());
+ }
});
this.to_replace = [];
return $.when.apply($, defs);
@@ -1304,7 +1312,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
var tagname = $tag[0].nodeName.toLowerCase();
if (this.tags_registry.contains(tagname)) {
this.tags_to_init.push($tag);
- return $tag;
+ return (tagname === 'button') ? this.process_button($tag) : $tag;
}
var fn = self['process_' + tagname];
if (fn) {
@@ -1321,6 +1329,13 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
return $tag;
}
},
+ process_button: function ($button) {
+ var self = this;
+ $button.children().each(function() {
+ self.process($(this));
+ });
+ return $button;
+ },
process_widget: function($widget) {
this.widgets_to_init.push($widget);
return $widget;
@@ -1820,7 +1835,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
this.$el.addClass(this.node.attrs["class"] || "");
},
destroy: function() {
- $.fn.tipsy.clear();
+ $.fn.tooltip('destroy');
this._super.apply(this, arguments);
},
/**
@@ -1852,9 +1867,8 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
widget = widget || this;
trigger = trigger || this.$el;
options = _.extend({
- delayIn: 500,
- delayOut: 0,
- fade: true,
+ delay: { show: 500, hide: 0 },
+ trigger: 'hover',
title: function() {
var template = widget.template + '.tooltip';
if (!QWeb.has_template(template)) {
@@ -1865,12 +1879,12 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
widget: widget
});
},
- gravity: $.fn.tipsy.autoBounds(50, 'nw'),
- html: true,
- opacity: 0.85,
- trigger: 'hover'
}, options || {});
- $(trigger).tipsy(options);
+ //only show tooltip if we are in debug or if we have a help to show, otherwise it will display
+ //as empty
+ if (instance.session.debug || widget.node.attrs.help || (widget.field && widget.field.help)){
+ $(trigger).tooltip(options);
+ }
},
/**
* Builds a new context usable for operations related to fields by merging
@@ -1906,6 +1920,8 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
template: 'WidgetButton',
init: function(field_manager, node) {
node.attrs.type = node.attrs['data-button-type'];
+ this.is_stat_button = /\boe_stat_button\b/.test(node.attrs['class']);
+ this.icon = node.attrs.icon && "<span class=\"fa " + node.attrs.icon + " fa-fw\"></span>";
this._super(field_manager, node);
this.force_disabled = false;
this.string = (this.node.attrs.string || '').replace(/_/g, '');
@@ -1941,26 +1957,23 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
var exec_action = function() {
if (self.node.attrs.confirm) {
var def = $.Deferred();
- var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), {
+ var dialog = new instance.web.Dialog(this, {
title: _t('Confirm'),
- modal: true,
buttons: [
{text: _t("Cancel"), click: function() {
- $(this).dialog("close");
+ this.parents('.modal').modal('hide');
}
},
{text: _t("Ok"), click: function() {
var self2 = this;
self.on_confirmed().always(function() {
- $(self2).dialog("close");
+ self2.parents('.modal').modal('hide');
});
}
}
],
- beforeClose: function() {
- def.resolve();
- },
- });
+ }, $('<div/>').text(self.node.attrs.confirm)).open();
+ dialog.on("closing", null, function() {def.resolve();});
return def.promise();
} else {
return self.on_confirmed();
@@ -1976,11 +1989,12 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
var self = this;
var context = this.build_context();
-
return this.view.do_execute_action(
_.extend({}, this.node.attrs, {context: context}),
- this.view.dataset, this.view.datarecord.id, function () {
- self.view.recursive_reload();
+ this.view.dataset, this.view.datarecord.id, function (reason) {
+ if (!_.isObject(reason)) {
+ self.view.recursive_reload();
+ }
});
},
check_disable: function() {
@@ -2107,8 +2121,8 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
this.$el.find('.oe_field_translate').click(this.on_translate);
}
this.$label = this.view ? this.view.$el.find('label[for=' + this.id_for_label + ']') : $();
+ this.do_attach_tooltip(this, this.$label[0] || this.$el);
if (instance.session.debug) {
- this.do_attach_tooltip(this, this.$label[0] || this.$el);
this.$label.off('dblclick').on('dblclick', function() {
console.log("Field '%s' of type '%s' in View: %o", self.name, (self.node.attrs.widget || self.field.type), self.view);
window.w = self;
@@ -2253,7 +2267,7 @@ instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.Reini
/**
Some hack to make placeholders work in ie9.
*/
-if ($.browser.msie && $.browser.version === "9.0") {
+if (!('placeholder' in document.createElement('input'))) {
document.addEventListener("DOMNodeInserted",function(event){
var nodename = event.target.nodeName.toLowerCase();
if ( nodename === "input" || nodename == "textarea" ) {
@@ -2423,6 +2437,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 model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
+ var domain = instance.web.pyeval.eval('domain', this.get('value'));
+ var ds = new instance.web.DataSetStatic(self, model, 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({
template: "web.datepicker",
jqueryui_object: 'datetimepicker',
@@ -2805,6 +2889,87 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
}
});
+/**
+ The PercentPie field expect a float from 0 to 100.
+*/
+instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
+ template: 'FieldPercentPie',
+
+ render_value: function() {
+ var value = this.get('value'),
+ formatted_value = Math.round(value || 0) + '%',
+ svg = this.$('svg')[0];
+
+ svg.innerHTML = "";
+ nv.addGraph(function() {
+ var width = 42, height = 42;
+ var chart = nv.models.pieChart()
+ .width(width)
+ .height(height)
+ .margin({top: 0, right: 0, bottom: 0, left: 0})
+ .donut(true)
+ .showLegend(false)
+ .showLabels(false)
+ .tooltips(false)
+ .color(['#7C7BAD','#DDD'])
+ .donutRatio(0.62);
+
+ d3.select(svg)
+ .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
+ .transition()
+ .call(chart)
+ .attr('style', 'width: ' + width + 'px; height:' + height + 'px;');
+
+ d3.select(svg)
+ .append("text")
+ .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'})
+ .style({"font-size": "10px", "font-weight": "bold"})
+ .text(formatted_value);
+
+ return chart;
+ });
+
+ }
+});
+
+/**
+ 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, {
template: 'FieldSelection',
@@ -2835,9 +3000,7 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan
var def;
if (this.field.type === "many2one") {
var model = new openerp.Model(openerp.session, this.field.relation);
- def = model.call("search", [this.get("domain")], {"context": this.build_context()}).then(function(record_ids) {
- return model.call("name_get", [record_ids] , {"context": self.build_context()});
- });
+ def = model.call("name_search", ['', this.get("domain")], {"context": this.build_context()});
} else {
var values = _.reject(this.field.selection, function (v) { return v[0] === false && v[1] === ''; });
def = $.when(values);
@@ -3127,7 +3290,8 @@ instance.web.form.CompletionFieldMixin = {
if (self.options.quick_create === undefined || self.options.quick_create) {
new instance.web.DataSet(this, this.field.relation, self.build_context())
.name_create(name).done(function(data) {
- self.add_id(data[0]);
+ if (!self.get('effective_readonly'))
+ self.add_id(data[0]);
}).fail(function(error, event) {
event.preventDefault();
slow_create();
@@ -3175,7 +3339,7 @@ instance.web.form.M2ODialog = instance.web.Dialog.extend({
init: function(parent) {
this._super(parent, {
title: _.str.sprintf(_t("Add %s"), parent.string),
- width: 312,
+ size: 'medium',
});
},
start: function() {
@@ -3243,7 +3407,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
delete this.$drop_down;
}
if (this.$input) {
- this.$input.closest(".ui-dialog .ui-dialog-content").off('scroll');
+ this.$input.closest(".modal .modal-content").off('scroll');
this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
'focus focusout change keydown');
delete this.$input;
@@ -3286,20 +3450,25 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
return;
}
var pop = new instance.web.form.FormOpenPopup(self);
- pop.show_element(
- self.field.relation,
- self.get("value"),
- self.build_context(),
- {
- title: _t("Open: ") + self.string
- }
- );
- pop.on('write_completed', self, function(){
- self.display_value = {};
- self.display_value_backup = {};
- self.render_value();
- self.focus();
- self.view.do_onchange(self);
+ var context = self.build_context().eval();
+ var model_obj = new instance.web.Model(self.field.relation);
+ model_obj.call('get_formview_id', [self.get("value"), context]).then(function(view_id){
+ pop.show_element(
+ self.field.relation,
+ self.get("value"),
+ self.build_context(),
+ {
+ title: _t("Open: ") + self.string,
+ view_id: view_id
+ }
+ );
+ pop.on('write_completed', self, function(){
+ self.display_value = {};
+ self.display_value_backup = {};
+ self.render_value();
+ self.focus();
+ self.view.do_onchange(self);
+ });
});
});
@@ -3336,7 +3505,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
self.$input.autocomplete("close");
}
}, 50);
- this.$input.closest(".ui-dialog .ui-dialog-content").on('scroll', this, close_autocomplete);
+ this.$input.closest(".modal .modal-content").on('scroll', this, close_autocomplete);
self.ed_def = $.Deferred();
self.uned_def = $.Deferred();
@@ -3361,7 +3530,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
}
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.uned_def.reject();
self.ed_def = $.Deferred();
@@ -3497,13 +3666,10 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
.html(link);
if (! this.options.no_open)
$link.click(function () {
- self.do_action({
- type: 'ir.actions.act_window',
- res_model: self.field.relation,
- res_id: self.get("value"),
- views: [[false, 'form']],
- target: 'current',
- context: self.build_context().eval(),
+ var context = self.build_context().eval();
+ var model_obj = new instance.web.Model(self.field.relation);
+ model_obj.call('get_formview_action', [self.get("value"), context]).then(function(action){
+ self.do_action(action);
});
return false;
});
@@ -4306,7 +4472,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
if (data.id) {
self.add_id(data.id);
} else {
- ignore_blur = true;
+ self.ignore_blur = true;
data.action();
}
this.trigger('setSuggestions', {result : []});
@@ -4346,7 +4512,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
if (this.get("effective_readonly"))
return;
var self = this;
- var ignore_blur = false;
+ self.ignore_blur = false;
self.$text = this.$("textarea");
self.$text.textext(self.initialize_texttext()).bind('getSuggestions', function(e, data) {
var _this = this;
@@ -4366,11 +4532,11 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
self.$text
.focusin(function () {
self.trigger('focused');
- ignore_blur = false;
+ self.ignore_blur = false;
})
.focusout(function() {
self.$text.trigger("setInputData", "");
- if (!ignore_blur) {
+ if (!self.ignore_blur) {
self.trigger('blurred');
}
}).keydown(function(e) {
@@ -4448,6 +4614,10 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
width: width,
minHeight: height
});
+ },
+ _search_create_popup: function() {
+ self.ignore_blur = true;
+ return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments);
},
});
@@ -4860,13 +5030,12 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
var self = this;
this.renderElement();
var dialog = new instance.web.Dialog(this, {
- min_width: '800px',
dialogClass: 'oe_act_window',
- close: function() {
- self.check_exit(true);
- },
title: this.options.title || "",
}, this.$el).open();
+ dialog.on('closing', this, function (e){
+ self.check_exit(true);
+ });
this.$buttonpane = dialog.$buttons;
this.start();
},
@@ -4933,8 +5102,8 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
},
destroy: function () {
this.trigger('closed');
- if (this.$el.is(":data(dialog)")) {
- this.$el.dialog('close');
+ if (this.$el.is(":data(bs.modal)")) {
+ this.$el.parents('.modal').modal('hide');
}
this._super();
},
@@ -5009,7 +5178,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
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]),
- contexts, groupbys);
+ contexts.concat(self.context), groupbys);
self.initial_ids = undefined;
} else {
self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys);
@@ -5855,6 +6024,36 @@ instance.web.form.X2ManyCounter = instance.web.form.AbstractField.extend(instanc
});
/**
+ This widget is intended to be used on stat button numeric fields. It will display
+ the value many2many and one2many. It is a read-only field that will
+ display a simple string "<value of field> <label of the field>"
+*/
+instance.web.form.StatInfo = instance.web.form.AbstractField.extend({
+ is_field_number: true,
+ init: function() {
+ this._super.apply(this, arguments);
+ this.internal_set_value(0);
+ },
+ set_value: function(value_) {
+ if (value_ === false || value_ === undefined) {
+ value_ = 0;
+ }
+ this._super.apply(this, [value_]);
+ },
+ render_value: function() {
+ var options = {
+ value: this.get("value") || 0,
+ };
+ if (! this.node.attrs.nolabel) {
+ options.text = this.string
+ }
+ this.$el.html(QWeb.render("StatInfo", options));
+ },
+
+});
+
+
+/**
* Registry of form fields, called by :js:`instance.web.FormView`.
*
* All referenced classes must implement FieldInterface. Those represent the classes whose instances
@@ -5867,6 +6066,7 @@ instance.web.form.widgets = new instance.web.Registry({
'url' : 'instance.web.form.FieldUrl',
'text' : 'instance.web.form.FieldText',
'html' : 'instance.web.form.FieldTextHtml',
+ 'char_domain': 'instance.web.form.FieldCharDomain',
'date' : 'instance.web.form.FieldDate',
'datetime' : 'instance.web.form.FieldDatetime',
'selection' : 'instance.web.form.FieldSelection',
@@ -5881,6 +6081,8 @@ instance.web.form.widgets = new instance.web.Registry({
'reference' : 'instance.web.form.FieldReference',
'boolean' : 'instance.web.form.FieldBoolean',
'float' : 'instance.web.form.FieldFloat',
+ 'percentpie': 'instance.web.form.FieldPercentPie',
+ 'barchart': 'instance.web.form.FieldBarChart',
'integer': 'instance.web.form.FieldFloat',
'float_time': 'instance.web.form.FieldFloat',
'progressbar': 'instance.web.form.FieldProgressBar',
@@ -5891,6 +6093,7 @@ instance.web.form.widgets = new instance.web.Registry({
'monetary': 'instance.web.form.FieldMonetary',
'many2many_checkboxes': 'instance.web.form.FieldMany2ManyCheckBoxes',
'x2many_counter': 'instance.web.form.X2ManyCounter',
+ 'statinfo': 'instance.web.form.StatInfo',
});
/**
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index e01ab29f914..16d0310ab4d 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -408,6 +408,9 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
if (total) {
var range_start = this.page * limit + 1;
var range_stop = range_start - 1 + limit;
+ if (this.records.length) {
+ range_stop = range_start - 1 + this.records.length;
+ }
if (range_stop > total) {
range_stop = total;
}
@@ -534,7 +537,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
[record.get('id')],
_.pluck(_(this.columns).filter(function (r) {
return r.tag === 'field';
- }), 'name')
+ }), 'name'),
+ {check_access_rule: true}
).done(function (records) {
var values = records[0];
if (!values) {
@@ -602,7 +606,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
_(ids).each(function (id) {
self.records.remove(self.records.get(id));
});
- self.configure_pager(self.dataset);
+ if (self.records.length === 0 && self.dataset.size() > 0) {
+ //Trigger previous manually to navigate to previous page,
+ //If all records are deleted on current page.
+ self.$pager.find('ul li:first a').trigger('click');
+ } else if (self.dataset.size() == self.limit()) {
+ //Reload listview to update current page with next page records
+ //because pager going to be hidden if dataset.size == limit
+ self.reload();
+ } else {
+ self.configure_pager(self.dataset);
+ }
self.compute_aggregates();
});
},
@@ -2247,8 +2261,8 @@ instance.web.list.Button = instance.web.list.Column.extend({
attrs = this.modifiers_for(row_data);
}
if (attrs.invisible) { return ''; }
-
- return QWeb.render('ListView.row.button', {
+ var template = this.icon && 'ListView.row.button' || 'ListView.row.text_button';
+ return QWeb.render(template, {
widget: this,
prefix: instance.session.prefix,
disabled: attrs.readonly
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index fc9af47dfe6..e2c63eb191a 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -126,10 +126,19 @@
* as an editable row at the top or bottom of the list)
*/
do_add_record: function () {
+ var self = this;
if (this.editable()) {
this.$el.find('table:first').show();
this.$el.find('.oe_view_nocontent').remove();
- this.start_edition();
+ this.start_edition().then(function(){
+ var fields = self.editor.form.fields;
+ self.editor.form.fields_order.some(function(field){
+ if (fields[field].$el.is(':visible')){
+ fields[field].$el.find("input").select();
+ return true;
+ }
+ });
+ });
} else {
this._super();
}
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index c9229c3c70a..b9015f6b311 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -25,9 +25,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
this._super.apply(this, arguments);
this.$el.on('click', 'a.oe_breadcrumb_item', this.on_breadcrumb_clicked);
},
- dialog_stop: function () {
+ dialog_stop: function (reason) {
if (this.dialog) {
- this.dialog.destroy();
+ this.dialog.destroy(reason);
}
this.dialog = null;
},
@@ -408,12 +408,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
if (this.dialog_widget && !this.dialog_widget.isDestroyed()) {
this.dialog_widget.destroy();
}
- this.dialog_stop();
+ this.dialog_stop(executor.action);
this.dialog = new instance.web.Dialog(this, {
+ title: executor.action.name,
dialogClass: executor.klass,
});
this.dialog.on("closing", null, options.on_close);
- this.dialog.dialog_title = executor.action.name;
if (widget instanceof instance.web.ViewManager) {
_.extend(widget.flags, {
$buttons: this.dialog.$buttons,
@@ -426,7 +426,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog.open();
return initialized;
} else {
- this.dialog_stop();
+ this.dialog_stop(executor.action);
this.inner_action = executor.action;
this.inner_widget = widget;
executor.post_process(widget);
@@ -581,7 +581,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
var self = this;
this.$el.find('.oe_view_manager_switch a').click(function() {
self.switch_mode($(this).data('view-type'));
- }).tipsy();
+ }).tooltip();
var views_ids = {};
_.each(this.views_src, function(view) {
self.views[view.view_type] = $.extend({}, view, {
@@ -639,7 +639,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
this.$el
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
.parent().addClass('active');
-
+ this.$el.attr("data-view-type", view_type);
return $.when(view_promise).done(function () {
_.each(_.keys(self.views), function(view_name) {
var controller = self.views[view_name].controller;
@@ -926,7 +926,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
current_view = this.views[this.active_view].controller;
switch (val) {
case 'fvg':
- var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open();
+ var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get") }).open();
$('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el);
break;
case 'tests':
@@ -943,7 +943,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
this.dataset.call('perm_read', [ids]).done(function(result) {
var dialog = new instance.web.Dialog(this, {
title: _.str.sprintf(_t("View Log (%s)"), self.dataset.model),
- width: 400
+ size: 'medium',
}, QWeb.render('ViewManagerDebugViewLog', {
perm : result[0],
format : instance.web.format_value
@@ -986,7 +986,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
new instance.web.Dialog(self, {
title: _.str.sprintf(_t("Model %s fields"),
self.dataset.model),
- width: '95%'}, $root).open();
+ }, $root).open();
});
break;
case 'edit_workflow':
@@ -1157,10 +1157,8 @@ instance.web.Sidebar = instance.web.Widget.extend({
this.$('.oe_form_dropdown_section').each(function() {
$(this).toggle(!!$(this).find('li').length);
});
-
- self.$("[title]").tipsy({
- 'html': true,
- 'delayIn': 500,
+ self.$("[title]").tooltip({
+ delay: { show: 500, hide: 0}
});
},
/**
@@ -1223,7 +1221,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
domain = $.Deferred().resolve(undefined);
}
if (ids.length === 0) {
- instance.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true });
+ new instance.web.Dialog(this, { title: _t("Warning"), size: 'medium',}, $("<div />").text(_t("You must choose at least one record."))).open();
return false;
}
var active_ids_context = {
@@ -1402,7 +1400,7 @@ instance.web.View = instance.web.Widget.extend({
// Wrong group_by values will simply fail and forbid rendering of the destination view
var ncontext = new instance.web.CompoundContext(
_.object(_.reject(_.pairs(dataset.get_context().eval()), function(pair) {
- return pair[0].match('^(?:(?:default_|search_default_).+|group_by|group_by_no_leaf|active_id|active_ids)$') !== null;
+ return pair[0].match('^(?:(?:default_|search_default_).+|.+_view_ref|group_by|group_by_no_leaf|active_id|active_ids)$') !== null;
}))
);
ncontext.add(action_data.context || {});
diff --git a/addons/web/static/src/js/watch.js b/addons/web/static/src/js/watch.js
new file mode 100644
index 00000000000..efa80a1d529
--- /dev/null
+++ b/addons/web/static/src/js/watch.js
@@ -0,0 +1,58 @@
+(function() {
+/*
+ * object.watch polyfill
+ *
+ * 2012-04-03
+ *
+ * By Eli Grey, http://eligrey.com
+ * Public Domain.
+ * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+ */
+
+// object.watch
+if (!Object.prototype.watch) {
+ Object.defineProperty(Object.prototype, "watch", {
+ enumerable: false
+ , configurable: true
+ , writable: false
+ , value: function (prop, handler) {
+ var
+ oldval = this[prop]
+ , newval = oldval
+ , getter = function () {
+ return newval;
+ }
+ , setter = function (val) {
+ oldval = newval;
+ return newval = handler.call(this, prop, oldval, val);
+ }
+ ;
+
+ if (delete this[prop]) { // can't watch constants
+ Object.defineProperty(this, prop, {
+ get: getter
+ , set: setter
+ , enumerable: true
+ , configurable: true
+ });
+ }
+ }
+ });
+}
+
+// object.unwatch
+if (!Object.prototype.unwatch) {
+ Object.defineProperty(Object.prototype, "unwatch", {
+ enumerable: false
+ , configurable: true
+ , writable: false
+ , value: function (prop) {
+ var val = this[prop];
+ delete this[prop]; // remove accessors
+ this[prop] = val;
+ }
+ });
+}
+
+})();
+
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 80b82c1f40b..24c5ba625b0 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -32,7 +32,20 @@
<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">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3 class="modal-title"><t t-raw="title"/></h3>
+ </div>
+ <div class="modal-body">
+ </div>
+ </div>
+ </div>
+ </div>
+</t>
<t t-name="CrashManager.warning">
<table cellspacing="0" cellpadding="0" border="0" class="oe_dialog_warning">
<tr>
@@ -231,17 +244,24 @@
</div>
<table align="center" class="db_option_table">
<tr>
- <td><label for="restore_db">File:</label></td>
- <td><input type="file" name="db_file" class="required" autofocus="autofocus"/></td>
+ <td><label for="restore_pwd">Master Password:</label></td>
+ <td><input type="password" name="restore_pwd" class="required" autofocus="autofocus"/></td>
</tr>
<tr>
- <td><label for="restore_pwd">Master Password:</label></td>
- <td><input type="password" name="restore_pwd" class="required"/></td>
+ <td><label for="restore_db">File:</label></td>
+ <td><input type="file" name="db_file" class="required"/></td>
</tr>
<tr>
<td><label for="new_db">New database name:</label></td>
<td><input type="text" name="new_db" class="required"/></td>
</tr>
+ <tr>
+ <td><label for="mode">Mode:</label></td>
+ <td><select name="mode" class="required">
+ <option value="restore">Backup Restore</option>
+ <option value="copy">Copy of an existing database</option>
+ </select></td>
+ </tr>
</table>
</form>
<form id="db_change_password" name="change_pwd_form" style="display: none;">
@@ -592,10 +612,16 @@
<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">
+ <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"/>
</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'">
@@ -772,11 +798,16 @@
</span>
</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"
t-att-title="widget.string" t-att-disabled="disabled || undefined"
- t-att-class="disabled ? 'oe_list_button_disabled' : undefined"
- ><img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png"
- t-att-alt="widget.string"/></button>
+ 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"
+ t-att-alt="widget.string"/></button>
<t t-extend="ListView.row">
<!-- adds back padding to row being rendered after edition, if necessary
(if not deletable add back padding), otherwise the row being added is
@@ -912,7 +943,6 @@
</t>
<t t-name="FormRenderingLabel">
<label t-att-for="_for"
- t-att-title="help"
t-attf-class="#{classnames} oe_form_label#{help ? '_help' : ''} oe_align_#{align}">
<t t-esc="string"/>
</label>
@@ -974,7 +1004,7 @@
</li>
<li t-if="widget.field and widget.field.selection" data-item="selection">
<span class="oe_tooltip_technical_title">Selection:</span>
- <ul>
+ <ul class="oe_tooltip_technical">
<li t-foreach="widget.field.selection" t-as="option">
[<t t-esc="option[0]"/>]
<t t-if="option[1]"> - </t>
@@ -1049,6 +1079,16 @@
</t>
</div>
</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">
<span>
<t t-set="placeholder" t-value="widget.getParent().node and widget.getParent().node.attrs.placeholder"/>
@@ -1188,6 +1228,18 @@
<span></span>
</span>
</t>
+<t t-name="FieldPercentPie">
+ <div class="oe_form_field oe_form_field_percent_pie" t-att-style="widget.node.attrs.style">
+ <svg></svg>
+ <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-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"/>
</t>
@@ -1252,7 +1304,7 @@
<td>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
- <t t-set="fileupload_style" t-translation="off">width: 83px;</t>
+ <t t-set="fileupload_style" t-translation="off">overflow-x: hidden</t>
<button class="oe_button oe_field_button" type="button">
<img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/>
<span>Select</span>
@@ -1351,13 +1403,15 @@
</div>
</t>
<t t-name="WidgetButton">
- <button type="button" class="oe_button oe_form_button"
+ <button type="button" t-att-class="widget.is_stat_button ? 'oe_stat_button btn btn-default' : 'oe_button oe_form_button'"
t-att-style="widget.node.attrs.style"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-accesskey="widget.node.attrs.accesskey">
- <img t-if="widget.node.attrs.icon" t-att-src="_s + widget.node.attrs.icon" width="16" height="16"/>
- <span t-if="widget.string"><t t-esc="widget.string"/></span>
+ <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 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>
+ <div t-if="widget.string and widget.is_stat_button"><t t-esc="widget.string"/></div>
</button>
</t>
<t t-name="WidgetButton.tooltip" t-extend="WidgetLabel.tooltip">
@@ -1930,4 +1984,7 @@
<t t-name="X2ManyCounter">
<a href="javascript:void(0)"><t t-esc="text"/></a>
</t>
+<t t-name="StatInfo">
+ <strong><t t-esc="value"/></strong>
+ <t t-esc="text"/></t>
</templates>