diff options
| author | Xavier Morel <xmo@openerp.com> | 2012-10-05 14:49:19 +0200 |
|---|---|---|
| committer | Xavier Morel <xmo@openerp.com> | 2012-10-05 14:49:19 +0200 |
| commit | b965ae5cf79f87a838d8a941f154390737895c4a (patch) | |
| tree | 168596c17d07c92af9bc29d3acc78f1ea5ecbf81 /addons/web/static/src | |
| parent | 6a23c24da13f5907d4c407e919bff187a3a0ae1d (diff) | |
| parent | c8c4086fd655b09f5fc0a4bde4d43d95745520f1 (diff) | |
[MERGE] from trunk
bzr revid: xmo@openerp.com-20121005124919-yfkbzk3mjm0tot4e
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/Makefile | 2 | ||||
| -rw-r--r-- | addons/web/static/src/css/base.css | 1099 | ||||
| -rw-r--r-- | addons/web/static/src/css/base.sass | 918 | ||||
| -rw-r--r-- | addons/web/static/src/css/data_import.css | 46 | ||||
| -rw-r--r-- | addons/web/static/src/js/boot.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 525 | ||||
| -rw-r--r-- | addons/web/static/src/js/corelib.js | 78 | ||||
| -rw-r--r-- | addons/web/static/src/js/coresetup.js | 262 | ||||
| -rw-r--r-- | addons/web/static/src/js/data.js | 395 | ||||
| -rw-r--r-- | addons/web/static/src/js/data_export.js | 132 | ||||
| -rw-r--r-- | addons/web/static/src/js/data_import.js | 393 | ||||
| -rw-r--r-- | addons/web/static/src/js/formats.js | 81 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 273 | ||||
| -rw-r--r-- | addons/web/static/src/js/test_support.js | 13 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 1390 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 462 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 89 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_tree.js | 26 | ||||
| -rw-r--r-- | addons/web/static/src/js/views.js | 525 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 452 |
20 files changed, 3813 insertions, 3353 deletions
diff --git a/addons/web/static/src/css/Makefile b/addons/web/static/src/css/Makefile index eca044497ef..d6b4f4b2fc8 100644 --- a/addons/web/static/src/css/Makefile +++ b/addons/web/static/src/css/Makefile @@ -1,3 +1,3 @@ base.css: base.sass - sass -t expanded base.sass base.css + sass --trace -t expanded base.sass base.css diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 8f35c81de2d..a5d24584fc5 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -20,55 +20,21 @@ font-style: normal; } -@-moz-keyframes bounce { - 0% { - -moz-transform: scale(0); - opacity: 0; - } - - 50% { - -moz-transform: scale(1.3); - opacity: 0.4; - } - - 75% { - -moz-transform: scale(0.9); - opacity: 0.7; - } - - 100% { - -moz-transform: scale(1); - opacity: 1; - } -} - -@-webkit-keyframes bounce { - 0% { - -webkit-transform: scale(0); - opacity: 0; - } - - 50% { - -webkit-transform: scale(1.3); - opacity: 0.4; - } - - 75% { - -webkit-transform: scale(0.9); - opacity: 0.7; - } - - 100% { - -webkit-transform: scale(1); - opacity: 1; +@media print { + .oe_topbar, .oe_leftbar, .oe_loading { + display: none !important; } } - .openerp.openerp_webclient_container { height: 100%; position: relative; } +.text-tag .text-button { + height: auto !important; + min-height: 16px; +} + .openerp { padding: 0; margin: 0; @@ -76,6 +42,7 @@ color: #4c4c4c; font-size: 13px; background: white; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); /* http://www.quirksmode.org/dom/inputfile.html * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image */ @@ -90,6 +57,7 @@ } .openerp a { text-decoration: none; + cursor: pointer !important; } .openerp table { padding: 0; @@ -135,13 +103,6 @@ background-image: -o-linear-gradient(top, #eeeeee, #dedede); background-image: linear-gradient(to bottom, #eeeeee, #dedede); } -.openerp ul, .openerp li, .openerp ol { - margin: 0; - padding: 0; -} -.openerp li { - list-style-type: none; -} .openerp input, .openerp textarea, .openerp select { padding: 2px 4px; border: 1px solid #cccccc; @@ -156,10 +117,10 @@ .openerp h4 { margin: 4px 0; } -.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'] { +.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'], .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { display: inline-block; - border: 1px solid #ababab; - color: #404040; + border: 1px solid rgba(0, 0, 0, 0.4); + color: #4c4c4c; margin: 0; padding: 3px 12px; font-size: 13px; @@ -181,7 +142,7 @@ -webkit-font-smoothing: antialiased; outline: none; } -.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover { +.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover { background-color: #f6f6f6; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -190,9 +151,11 @@ background-image: -o-linear-gradient(top, #f6f6f6, #e3e3e3); background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); cursor: pointer; + background-position: 0; } -.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus { +.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus { border: 1px solid #80bfff; + background-position: 0; background-color: #f6f6f6; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -204,7 +167,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 button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active { +.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active { background-color: #e3e3e3; background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)); background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6); @@ -226,22 +189,11 @@ cursor: default; text-shadow: 0 1px 1px white !important; } -.openerp .ui-widget { - font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; - font-size: 13px; -} -.openerp .ui-menu .ui-menu-item { - margin: 0 8px 0 0; - padding: 0 0 0 12px; - width: auto; +.openerp .ui-widget-content a { + color: #7c7bad; } -.openerp .ui-menu .ui-menu-item a.ui-state-active { - background: #f0f0fa; -} -.openerp div.ui-widget-overlay { - background: black; - filter: alpha(opacity=30); - opacity: 0.3; +.openerp .oe_bounce_container { + display: inline-block; } .openerp.ui-dialog { display: none; @@ -297,11 +249,14 @@ border-radius: 0 0 2px 2px; } .openerp.ui-dialog .ui-dialog-buttonpane button { - margin: 0; + 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; } @@ -332,11 +287,14 @@ border-radius: 0 0 2px 2px; } .openerp.ui-dialog .oe_about a { - color: #8a89ba; + 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; } @@ -419,40 +377,37 @@ background: #dc5f59; } .openerp button.oe_highlight { - background-color: #dc5f59; - background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); - background-image: -webkit-linear-gradient(top, #dc5f59, #b33630); - background-image: -moz-linear-gradient(top, #dc5f59, #b33630); - background-image: -ms-linear-gradient(top, #dc5f59, #b33630); - background-image: -o-linear-gradient(top, #dc5f59, #b33630); - background-image: linear-gradient(to bottom, #dc5f59, #b33630); - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; + background-color: #df3f3f; + background-image: -webkit-gradient(linear, left top, left bottom, from(#df3f3f), to(#a21a1a)); + background-image: -webkit-linear-gradient(top, #df3f3f, #a21a1a); + background-image: -moz-linear-gradient(top, #df3f3f, #a21a1a); + background-image: -ms-linear-gradient(top, #df3f3f, #a21a1a); + background-image: -o-linear-gradient(top, #df3f3f, #a21a1a); + background-image: linear-gradient(to bottom, #df3f3f, #a21a1a); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; +} +.openerp button.oe_highlight:hover { + background-color: #e25858; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e25858), to(#ab1b1b)); + background-image: -webkit-linear-gradient(top, #e25858, #ab1b1b); + background-image: -moz-linear-gradient(top, #e25858, #ab1b1b); + background-image: -ms-linear-gradient(top, #e25858, #ab1b1b); + background-image: -o-linear-gradient(top, #e25858, #ab1b1b); + background-image: linear-gradient(to bottom, #e25858, #ab1b1b); } .openerp button.oe_highlight:active { - background-color: #b33630; - background-image: -webkit-gradient(linear, left top, left bottom, from(#b33630), to(#dc5f59)); - background-image: -webkit-linear-gradient(top, #b33630, #dc5f59); - background-image: -moz-linear-gradient(top, #b33630, #dc5f59); - background-image: -ms-linear-gradient(top, #b33630, #dc5f59); - background-image: -o-linear-gradient(top, #b33630, #dc5f59); - background-image: linear-gradient(to bottom, #b33630, #dc5f59); - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; + background-color: #c52020; + background-image: -webkit-gradient(linear, left top, left bottom, from(#c52020), to(#d22323)); + background-image: -webkit-linear-gradient(top, #c52020, #d22323); + background-image: -moz-linear-gradient(top, #c52020, #d22323); + background-image: -ms-linear-gradient(top, #c52020, #d22323); + background-image: -o-linear-gradient(top, #c52020, #d22323); + background-image: linear-gradient(to bottom, #c52020, #d22323); } -.openerp button.oe_highlight:hover { - background-color: #df6b66; - background-image: -webkit-gradient(linear, left top, left bottom, from(#df6b66), to(#bf3a33)); - background-image: -webkit-linear-gradient(top, #df6b66, #bf3a33); - background-image: -moz-linear-gradient(top, #df6b66, #bf3a33); - background-image: -ms-linear-gradient(top, #df6b66, #bf3a33); - background-image: -o-linear-gradient(top, #df6b66, #bf3a33); - background-image: linear-gradient(to bottom, #df6b66, #bf3a33); - -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); +.openerp .oe_background_grey { + background: #eeeeee !important; } .openerp .oe_form_dirty .oe_highlight_on_dirty { color: white; @@ -492,31 +447,37 @@ .openerp .oe_button_box button { margin: 4px; } -.openerp .oe_avatar { - margin: 0 6px 15px 0; -} .openerp .oe_avatar > img { - height: 90px; - max-width: 100px; + max-height: 90px; + max-width: 90px; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); border: none; + margin-bottom: 10px; } .openerp .oe_avatar + div { margin-left: 5px; } +.openerp .oe_image_small > img { + max-width: 50px; + max-height: 50px; +} +.openerp .oe_image_medium > img { + max-width: 180px; + max-height: 180px; +} .openerp .oe_button.oe_link { border: none; padding: 0; margin: 0; background: none; - -moz-border-radius: none; - -webkit-border-radius: none; - border-radius: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; @@ -529,13 +490,13 @@ padding: 0; margin: 0; background: none; - -moz-border-radius: none; - -webkit-border-radius: none; - border-radius: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; - color: #8a89ba; + color: #7c7bad; font-weight: bold; } .openerp .oe_button.oe_link span:hover { @@ -554,22 +515,48 @@ .openerp .oe_webclient .oe_star_on { color: gold; } -.openerp .oe_bounce { - -moz-animation: bounce 0.4s linear; - -webkit-animation: bounce 0.4s linear; +.openerp header { + position: relative; + border-bottom: 1px solid #cacaca; + padding-left: 2px; + background-color: #fcfcfc; + 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 header > span { + margin-left: 4px; +} +.openerp header ul { + display: inline-block; + float: right; +} +.openerp header .oe_button { + margin: 3px 2px 1px; } .openerp .oe_tag { - border-radius: 2px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #9daccc; - background: #e2e6f0; - color: black !important; - padding: 0px 3px 0px 3px; + border: 1px solid #afafb6; + font-size: 11px; + padding: 2px 4px; margin: 0 2px 2px 0; - height: 16px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: #f0f0fa; + color: #4c4c4c; +} +.openerp .oe_tag_dark { + background: #7c7bad; + color: #eeeeee; +} +.openerp .oe_tags { + margin-bottom: 1px; +} +.openerp .oe_tags.oe_inline { + min-width: 250px; } .openerp .oe_tags .text-wrap { width: 100% !important; @@ -602,6 +589,19 @@ .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; @@ -670,7 +670,7 @@ cursor: pointer; } .openerp .oe_dropdown_toggle { - color: #404040; + color: rgba(0, 0, 0, 0.5); font-weight: normal; } .openerp .oe_dropdown_hover:hover .oe_dropdown_menu, .openerp .oe_dropdown_menu.oe_opened { @@ -681,7 +681,9 @@ position: absolute; top: 26px; left: 0; - z-index: 1; + z-index: 3; + margin: 0; + padding: 0; border: 1px solid #afafb6; background: white; padding: 4px 0; @@ -699,6 +701,7 @@ float: none; display: block; position: relative; + margin: 0; padding: 2px 8px; } .openerp .oe_dropdown_menu > li:hover { @@ -730,7 +733,7 @@ text-indent: -99999px; vertical-align: top; margin-top: 8px; - margin-left: 4px; + margin-left: 3px; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 4px solid #404040; @@ -816,9 +819,10 @@ font-size: 14px; height: 100%; } -.openerp .oe_login li { +.openerp .oe_login ul, .openerp .oe_login li { + margin: 0; list-style-type: none; - padding-bottom: 4px; + padding: 0 0 4px 0; } .openerp .oe_login button { float: right; @@ -847,9 +851,9 @@ font-family: "Lucida Grande", Helvetica, Verdana, Arial; border: 1px solid #999999; background: whitesmoke; - -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4); -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; @@ -980,8 +984,8 @@ } .openerp .oe_topbar { width: 100%; - height: 31px; - border-top: solid 1px #d3d3d3; + height: 32px; + background-color: #414141; background-color: #646060; background-image: -webkit-gradient(linear, left top, left bottom, from(#646060), to(#262626)); background-image: -webkit-linear-gradient(top, #646060, #262626); @@ -995,24 +999,23 @@ padding: 5px 10px 7px; line-height: 20px; height: 20px; + text-decoration: none; color: #eeeeee; vertical-align: top; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; } .openerp .oe_topbar .oe_topbar_item:hover { - background: #303030; - color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -} -.openerp .oe_topbar .oe_topbar_item .oe_active { - background: #303030; - font-weight: bold; + background: rgba(0, 0, 0, 0.2); + text-shadow: black 0px 0px 3px; color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; } .openerp .oe_topbar .oe_topbar_avatar { width: 24px; @@ -1062,6 +1065,69 @@ -webkit-box-shadow: none; box-shadow: none; } +.openerp .oe_menu { + float: left; + padding: 0; + margin: 0; + font-size: 13px; +} +.openerp .oe_menu > li { + list-style-type: none; + padding: 0; + margin: 0; + float: left; + display: block; + color: #eeeeee; +} +.openerp .oe_menu > li > a { + display: block; + padding: 5px 10px 7px; + line-height: 20px; + height: 20px; + text-decoration: none; + color: #eeeeee; + vertical-align: top; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} +.openerp .oe_menu > li > a:hover { + background: rgba(0, 0, 0, 0.2); + text-shadow: black 0px 0px 3px; + color: white; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; +} +.openerp .oe_menu > li > .oe_active { + background: rgba(0, 0, 0, 0.3); + text-shadow: black 0px 0px 3px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; +} +.openerp .oe_user_menu { + float: right; + padding: 0; + margin: 0; +} +.openerp .oe_user_menu li { + list-style-type: none; + float: left; +} +.openerp .oe_user_menu .oe_dropdown_menu { + right: -1px; +} +.openerp .oe_systray > div { + float: left; + padding: 0 4px 0 4px; +} +.openerp .oe_systray { + float: right; +} .openerp .oe_leftbar { display: none; width: 220px; @@ -1100,61 +1166,10 @@ color: #c81010; font-style: italic; } -.openerp .oe_user_menu { - float: right; - padding: 0; - margin: 0; -} -.openerp .oe_user_menu li { - list-style-type: none; - float: left; -} -.openerp .oe_user_menu .oe_dropdown_menu { - right: -1px; -} -.openerp .oe_systray > div { - float: left; - padding: 0 4px 0 4px; -} -.openerp .oe_systray { - float: right; -} -.openerp .oe_menu { - float: left; - padding: 0; - margin: 0; -} -.openerp .oe_menu li { - float: left; -} -.openerp .oe_menu a { - display: block; - padding: 5px 10px 7px; - line-height: 20px; - height: 20px; - color: #eeeeee; - vertical-align: top; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -} -.openerp .oe_menu a:hover { - background: #303030; - color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -} -.openerp .oe_menu .oe_active { - background: #303030; - font-weight: bold; - color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -} .openerp .oe_secondary_menu_section { font-weight: bold; margin-left: 8px; - color: #8a89ba; + color: #7c7bad; } .openerp .oe_secondary_submenu { padding: 2px 0 8px 0; @@ -1162,21 +1177,23 @@ width: 100%; display: inline-block; } -.openerp .oe_secondary_submenu > li { +.openerp .oe_secondary_submenu li { position: relative; - padding: 1px 0 1px 20px; + margin: 0; + padding: 1px 0 1px 20px !important; + list-style-type: none; } -.openerp .oe_secondary_submenu > li a { +.openerp .oe_secondary_submenu li a { display: block; color: #4c4c4c; padding: 2px 4px 2px 0; } -.openerp .oe_secondary_submenu > li .oe_menu_label { +.openerp .oe_secondary_submenu li .oe_menu_label { position: absolute; top: 1px; right: 1px; font-size: 10px; - background: #8a89ba; + background: #7c7bad; color: white; padding: 2px 4px; margin: 1px 6px 0 0; @@ -1191,32 +1208,34 @@ } .openerp .oe_secondary_submenu .oe_menu_counter { float: right; - background: #8a89ba; - color: #eeeeee; - font-size: 10px; - border: 1px solid lightgray; - padding: 2px 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - margin: 0px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; + margin: 1px; + padding: 1px 4px; + border: none; +} +.openerp .oe_secondary_submenu .oe_menu_counter:hover { + cursor: pointer; + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } .openerp .oe_secondary_submenu .oe_active { - background: #8a89ba; border-top: 1px solid lightGray; - border-bottom: 1px solid lightGray; + border-bottom: 1px solid #dedede; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); + -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); + background: #7c7bad; } .openerp .oe_secondary_submenu .oe_active a { color: white; } .openerp .oe_secondary_submenu .oe_active .oe_menu_label { background: #eeeeee; - color: #8a89ba; + color: #7c7bad; text-shadow: 0 1px 1px white; -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); @@ -1224,7 +1243,10 @@ } .openerp .oe_secondary_submenu .oe_active .oe_menu_counter { background: #eeeeee; - color: #8a89ba; + color: #7c7bad; + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); } .openerp .oe_secondary_submenu .oe_menu_toggler:before { width: 0; @@ -1250,11 +1272,53 @@ border-right: 4px solid transparent; border-top: 4px solid #4c4c4c; } +.openerp .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 .oe_about a { + color: #7c7bad; +} +.openerp .oe_about a:hover { + text-decoration: underline; +} +.openerp .oe_about a:focus { + outline: none; +} +.openerp .oe_about .oe_logo { + 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: #b41616; + 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 .oe_about .oe_bottom a { + color: #eeeeee; +} .openerp .oe_application { width: 100%; } .openerp .oe_application a { - color: #8a89ba; + color: #7c7bad; } .openerp .oe_application a:hover { text-decoration: underline; @@ -1262,6 +1326,13 @@ .openerp .oe_application > div { height: 100%; } +.openerp .oe_application .oe_breadcrumb_item:not(:last-child) { + display: inline-block; + max-width: 7em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} .openerp .oe_view_manager .oe_view_manager_body { height: inherit; } @@ -1279,6 +1350,9 @@ .openerp .oe_view_manager table.oe_view_manager_header .oe_header_row:last-child td { padding-top: 0; } +.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row:first-child td { + padding-top: 8px; +} .openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_sidebar { margin: 0px auto; text-align: center; @@ -1292,7 +1366,10 @@ float: left; } .openerp .oe_view_manager table.oe_view_manager_header h2 a { - color: #8a89ba; + color: #7c7bad; +} +.openerp .oe_view_manager table.oe_view_manager_header .oe_dropdown_menu { + line-height: normal; } .openerp .oe_view_manager table.oe_view_manager_header .oe_button_group { display: inline-block; @@ -1337,6 +1414,10 @@ float: left; margin-right: 8px; } +.openerp .oe_view_manager ul.oe_pager_group { + padding: 0; + margin: 0; +} .openerp .oe_view_manager .oe_pager_group { float: left; height: 24px; @@ -1352,6 +1433,8 @@ height: 24px; line-height: 24px; padding: 0; + margin: 0; + list-style-type: none; float: left; border-right: 1px solid #ababab; } @@ -1375,11 +1458,18 @@ color: white; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } +.openerp .oe_view_manager .oe_view_manager_switch { + padding: 0; + margin: 0 0 0 8px; +} .openerp .oe_view_manager .oe_view_manager_switch li { - text-align: center; + margin: 0; width: 24px; height: 24px; line-height: 16px; + padding: 0; + text-align: center; + list-style-type: none; } .openerp .oe_view_manager .oe_view_manager_switch li a { position: relative; @@ -1420,7 +1510,6 @@ height: 100%; } .openerp .oe_view_manager_current > .oe_view_manager_header { - border-top: 1px solid #cacaca; border-bottom: 1px solid #cacaca; background-color: #fcfcfc; background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); @@ -1439,10 +1528,10 @@ .openerp .oe_view_manager_current > .oe_view_manager_header .oe_header_row:first-child td { padding-top: 8px; } -.openerp .oe_view_manager_inline { +.openerp .oe_view_manager_inline, .openerp .oe_view_manager_inlineview { height: 100%; } -.openerp .oe_view_manager_inline > .oe_view_manager_header { +.openerp .oe_view_manager_inline > .oe_view_manager_header, .openerp .oe_view_manager_inlineview > .oe_view_manager_header { display: none; } .openerp .oe_popup_form > .oe_formview > .oe_form_pager { @@ -1516,6 +1605,7 @@ font-size: 1px; letter-spacing: -1px; color: transparent; + font-weight: normal; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; @@ -1543,25 +1633,27 @@ display: inline-block; line-height: 17px; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { height: 18px; margin: 1px 0; font-size: 11px; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { outline: none; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_input { padding: 0 0 0 6px; + font-size: 12px; + height: 16px; + margin-top: 3px; +} +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus { + outline: none; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { position: relative; cursor: pointer; - border: 1px solid #afafb6; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - background: #8786b7; + padding: 0; -webkit-font-smoothing: auto; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { @@ -1590,6 +1682,7 @@ .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value { border-left: 1px solid #afafb6; text-shadow: 0 1px 1px white; + color: #4c4c4c; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:last-child { padding-right: 16px; @@ -1602,6 +1695,7 @@ line-height: 8px; width: 12px; height: 12px; + padding-top: 1px; text-align: center; font-weight: bold; cursor: pointer; @@ -1647,7 +1741,7 @@ } .openerp .oe_searchview .oe_searchview_drawer h3 { margin: 8px 4px 4px 12px; - color: #8786b7; + color: #7c7bad; font-size: 13px; } .openerp .oe_searchview .oe_searchview_drawer h4, .openerp .oe_searchview .oe_searchview_drawer h4 * { @@ -1770,6 +1864,13 @@ margin: 0; white-space: nowrap; } +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced li:first-child .searchview_extended_prop_or { + visibility: hidden; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced .searchview_extended_prop_or { + opacity: 0.5; + margin-left: -14px; +} .openerp .oe_searchview .oe_searchview_drawer .oe_opened h4:before { content: "â–¾ "; position: relative; @@ -1901,147 +2002,112 @@ width: auto; } .openerp .oe_form_nosheet { - margin: 20px; + margin: 16px; } .openerp .oe_form_nosheet > header { - margin-top: -20px; - margin-left: -20px; - margin-right: -20px; -} -.openerp .oe_form header { - position: relative; - border-bottom: 1px solid #cacaca; - background-color: #fcfcfc; - 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); - padding: 0 8px; - line-height: 30px; + margin: -16px -16px 0 -16px; + padding: 0; } -.openerp .oe_form header ul { - display: inline-block; - float: right; +.openerp .oe_form_sheetbg { + padding: 16px 0; } -.openerp .oe_form div.oe_chatter { +.openerp .oe_form_sheet_width { min-width: 650px; max-width: 860px; margin: 0 auto; } -.openerp .oe_form div.oe_form_configuration div.oe_horizontal_separator { - margin: 25px 0 10px 0; +.openerp .oe_form_sheet { + background: white; + min-height: 330px; + padding: 16px; } -.openerp .oe_form div.oe_form_configuration p { - color: #aaaaaa; - max-width: 650px; +.openerp .oe_application .oe_form_sheetbg { + background: url(/web/static/src/img/form_sheetbg.png); + border-bottom: 1px solid #dddddd; } -.openerp .oe_form div.oe_form_configuration label { - min-width: 150px; +.openerp .oe_application .oe_form_sheet { + border: 1px solid #c8c8d3; + -moz-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + -webkit-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); } -.openerp ul.oe_form_steps { - height: 30px; - padding: 0; - margin: 0; - text-shadow: 0 1px 1px white; +.openerp .oe_application .oe_form_sheet .ui-tabs { + margin: 0 -16px; } -.openerp ul.oe_form_steps img { - vertical-align: top; - margin-left: 8px; +.openerp .oe_application .oe_form_sheet .oe_notebook_page { + padding: 0 16px; } -.openerp ul.oe_form_steps li { - border-right: none; - padding: 0; - margin: 0; - float: left; - vertical-align: top; - height: 30px; - padding: 0 0 0 12px; +.openerp .oe_form header .oe_tags { + margin: 5px 0 0 5px; + width: 400px; + padding-bottom: 0; } -.openerp ul.oe_form_steps li:first-child { - border-left: 1px solid #cacaca; +.openerp .oe_form header .oe_tags div.oe_chatter { + min-width: 650px; + max-width: 860px; + margin: 0 auto; + padding: 16px 0 48px; } -.openerp ul.oe_form_steps li:last-child { - margin-right: 12px; - padding-right: 12px; - border-right: 1px solid #cacaca; +.openerp .oe_form header .oe_tags .oe_grey { + color: #aaaaaa; + max-width: 650px; + margin: 0 0 10px 0; } -.openerp ul.oe_form_steps li a { - color: #4c4c4c; +.openerp .oe_form header .oe_tags div.oe_form_configuration p, .openerp .oe_form header .oe_tags div.oe_form_configuration ul, .openerp .oe_form header .oe_tags div.oe_form_configuration ol { + color: #aaaaaa; + max-width: 650px; } -.openerp ul.oe_form_steps li a:hover { - color: black; +.openerp .oe_form header .oe_tags div.oe_form_configuration label { + min-width: 150px; } -.openerp ul.oe_form_steps .oe_form_steps_active { - font-weight: bold; - color: #b33630; +.openerp .oe_form header .oe_tags div.oe_form_configuration .oe_form_group_cell_label { + padding: 1px 0; } -.openerp .oe_form .oe_subtotal_footer { +.openerp .oe_form header .oe_tags div.oe_form_configuration .oe_form_group_cell div div { + padding: 1px 0; +} +.openerp .oe_form header .oe_tags .oe_subtotal_footer { width: 1% !important; } -.openerp .oe_form .oe_subtotal_footer td.oe_form_group_cell { +.openerp .oe_form header .oe_tags .oe_subtotal_footer td.oe_form_group_cell { text-align: right; padding: 0 !important; } -.openerp .oe_form .oe_subtotal_footer td.oe_form_group_cell_label { +.openerp .oe_form header .oe_tags .oe_subtotal_footer td.oe_form_group_cell_label { border-right: none; } -.openerp .oe_form .oe_subtotal_footer .oe_subtotal_footer_separator { +.openerp .oe_form header .oe_tags .oe_subtotal_footer .oe_subtotal_footer_separator { width: 108px; border-top: 1px solid #cacaca; + margin-top: 4px; + padding-top: 4px; font-weight: bold; font-size: 18px; } -.openerp .oe_form .oe_subtotal_footer label:after { +.openerp .oe_form header .oe_tags .oe_subtotal_footer label:after { content: ":"; } -.openerp .oe_form .oe_subtotal_footer label.oe_subtotal_footer_separator { +.openerp .oe_form header .oe_tags .oe_subtotal_footer label.oe_subtotal_footer_separator { font-weight: bold !important; padding: 2px 11px 2px 0px !important; } -.openerp .oe_form .oe_subtotal_footer label.oe_form_label_help { +.openerp .oe_form header .oe_tags .oe_subtotal_footer label.oe_form_label_help { font-weight: normal; } -.openerp .oe_application .oe_form_sheetbg { - background: url(/web/static/src/img/form_sheetbg.png); - padding: 8px 0; - border-bottom: 1px solid #dddddd; -} -.openerp .oe_application .oe_form_sheet_width { - min-width: 650px; - max-width: 860px; - margin: 0 auto; -} -.openerp .oe_application .oe_form_sheet { - background: white; - min-height: 330px; - padding: 16px; - border: 1px solid #afafb6; - -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); -} -.openerp .oe_application .oe_form_sheet .ui-tabs { - margin: 0 -16px; -} -.openerp .oe_application .oe_form_sheet .oe_notebook_page { - padding: 0 16px; -} .openerp .oe_form .oe_form_button { margin: 2px; } .openerp .oe_form td.oe_form_group_cell_label { border-right: 1px solid #dddddd; - padding: 4px 0px 4px 0px; + padding: 2px 0px; } .openerp .oe_form td.oe_form_group_cell_label label { line-height: 18px; display: block; - min-width: 120px; + min-width: 140px; } .openerp .oe_form td.oe_form_group_cell + .oe_form_group_cell { - padding-left: 6px; + padding: 2px 0 2px 8px; } .openerp .oe_form .oe_form_group { width: 100%; @@ -2053,7 +2119,7 @@ .openerp .oe_form .oe_form_label_help[for], .openerp .oe_form .oe_form_label[for] { font-weight: bold; white-space: nowrap; - padding-right: 6px; + padding-right: 8px; } .openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span { font-size: 80%; @@ -2069,8 +2135,8 @@ .openerp .oe_horizontal_separator { font-weight: bold; font-size: 20px; - margin: 8px 0px 8px 0px; - color: #53637e; + margin: 15px 0px 10px 0px; + color: #7c7bad; } .openerp .oe_horizontal_separator:empty { height: 5px; @@ -2129,7 +2195,6 @@ width: 100%; display: inline-block; padding: 2px 2px 2px 0px; - line-height: 18px; } .openerp .oe_form .oe_form_field input { margin: 0px; @@ -2144,7 +2209,7 @@ -ms-box-sizing: border-box; box-sizing: border-box; background: white; - min-width: 70px; + min-width: 60px; color: #1f1f1f; } .openerp .oe_form input[readonly], .openerp .oe_form select[readonly], .openerp .oe_form textarea[readonly], .openerp .oe_form input[disabled], .openerp .oe_form select[disabled] { @@ -2153,7 +2218,6 @@ } .openerp .oe_form textarea[disabled] { border: none; - border-left: 8px solid #eeeeee; padding-left: 8px; -moz-box-shadow: none; -webkit-box-shadow: none; @@ -2162,6 +2226,9 @@ -webkit-border-radius: 0px; border-radius: 0px; } +.openerp .oe_form textarea.oe_inline[disabled] { + border-left: 8px solid #eeeeee; +} .openerp .oe_form .oe_form_field_url button img { vertical-align: top; } @@ -2179,7 +2246,7 @@ .openerp .oe_form .oe_datepicker_root { display: inline-block; } -.openerp .oe_form .oe_form_required input, .openerp .oe_form .oe_form_required select, .openerp .oe_form .oe_form_required textarea { +.openerp .oe_form .oe_form_required input:not([disabled]), .openerp .oe_form .oe_form_required select:not([disabled]), .openerp .oe_form .oe_form_required textarea:not([disabled]) { background-color: #d2d2ff !important; } .openerp .oe_form .oe_form_invalid input, .openerp .oe_form .oe_form_invalid select, .openerp .oe_form .oe_form_invalid textarea { @@ -2217,11 +2284,19 @@ position: relative; overflow: hidden; } +.openerp .oe_form .oe_form_field_html { + position: relative; + width: 600px; + margin-left: 130px; + margin-top: 32px; + margin-bottom: 32px; + text-align: justify; +} .openerp .oe_form_editable .oe_form .oe_form_field_integer { - width: 7em !important; + width: 6em !important; } .openerp .oe_form_editable .oe_form .oe_form_field_float { - width: 8em !important; + width: 7em !important; } .openerp .oe_form_editable .oe_form .oe_form_field_date { width: 7.5em !important; @@ -2230,11 +2305,7 @@ width: 11.5em !important; } .openerp .oe_hidden_input_file { - overflow: hidden; position: relative; - display: inline-block; - width: 45px; - height: 30px; } .openerp .oe_hidden_input_file input.oe_form_binary_file { z-index: 0; @@ -2242,7 +2313,8 @@ font-size: 12px; position: absolute; top: 1px; - right: 10px; + left: 0; + right: 0; opacity: 0; filter: alpha(opacity=0); -ms-filter: "alpha(opacity=0)"; @@ -2258,15 +2330,16 @@ } .openerp .oe_form .oe_form_field_image .oe_form_field_image_controls { position: absolute; - white-space: nowrap; top: 1px; - padding: 3px 0 0 0; - margin: 0 1px; + padding: 4px; + width: 100%; display: none; - width: 99%; text-align: center; - background: url(/web/static/src/img/form_sheetbg.png); - border-bottom: 1px dotted black; + color: #eeeeee; + background: rgba(37, 37, 37, 0.9); + -moz-border-radius: 3px 3px 0 0; + -webkit-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -ms-box-sizing: border-box; @@ -2285,10 +2358,157 @@ } .openerp .oe_form_field_many2one .oe_m2o_cm_button { line-height: 14px; + float: right; + padding-left: 2px; +} +.openerp.ui-autocomplete li.oe_m2o_dropdown_option a { + font-style: italic; + padding-left: 2em; +} +.openerp.ui-autocomplete li:not(.oe_m2o_dropdown_option) + li.oe_m2o_dropdown_option { + margin-top: 10px; +} +.openerp ul.oe_form_status, .openerp ul.oe_form_status_clickable { + display: inline-block; + margin: 0; + padding: 0 18px 0 0; +} +.openerp ul.oe_form_status li, .openerp ul.oe_form_status_clickable li { + display: inline-block; + list-style-type: none; + margin: 0 -18px 0 0; + padding: 0; + background-color: #fcfcfc; + 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 ul.oe_form_status li:first-child .label, .openerp ul.oe_form_status_clickable li:first-child .label { + border-left: 1px solid #cacaca; + padding-left: 14px; +} +.openerp ul.oe_form_status li:last-child, .openerp ul.oe_form_status_clickable li:last-child { + border-right: 1px solid #cacaca; +} +.openerp ul.oe_form_status li:last-child .label, .openerp ul.oe_form_status_clickable li:last-child .label { + padding-right: 14px; +} +.openerp ul.oe_form_status li:last-child .arrow, .openerp ul.oe_form_status_clickable li:last-child .arrow { + display: none; +} +.openerp ul.oe_form_status li .label, .openerp ul.oe_form_status_clickable li .label { + color: #4c4c4c; + text-shadow: 0 1px 1px #fcfcfc, 0 -1px 1px #dedede; + padding: 7px; + display: inline-block; + padding-left: 24px; + margin: 0; + position: relative; +} +.openerp ul.oe_form_status li .arrow, .openerp ul.oe_form_status_clickable li .arrow { + width: 17px; + display: inline-block; + vertical-align: top; + overflow: hidden; + margin-left: -5px; +} +.openerp ul.oe_form_status li .arrow span, .openerp ul.oe_form_status_clickable li .arrow span { + position: relative; + width: 24px; + height: 24px; + display: inline-block; + margin-left: -12px; + margin-top: 3px; + box-shadow: -1px 1px 2px rgba(255, 255, 255, 0.2), inset -1px 1px 1px rgba(0, 0, 0, 0.2); + background-color: #dedede; + background: -moz-linear-gradient(135deg, #dedede, #fcfcfc); + background: -o-linear-gradient(135deg, #fcfcfc, #dedede); + background: -webkit-gradient(linear, left top, right bottom, from(#fcfcfc), to(#dedede)); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); +} +.openerp ul.oe_form_status li.oe_active, .openerp ul.oe_form_status_clickable li.oe_active { + background-color: #729fcf; + background-image: -webkit-gradient(linear, left top, left bottom, from(#729fcf), to(#3465a4)); + background-image: -webkit-linear-gradient(top, #729fcf, #3465a4); + background-image: -moz-linear-gradient(top, #729fcf, #3465a4); + background-image: -ms-linear-gradient(top, #729fcf, #3465a4); + background-image: -o-linear-gradient(top, #729fcf, #3465a4); + background-image: linear-gradient(to bottom, #729fcf, #3465a4); +} +.openerp ul.oe_form_status li.oe_active .arrow span, .openerp ul.oe_form_status_clickable li.oe_active .arrow span { + background-color: #3465a4; + background: -moz-linear-gradient(135deg, #3465a4, #729fcf); + background: -o-linear-gradient(135deg, #729fcf, #3465a4); + background: -webkit-gradient(linear, left top, right bottom, from(#729fcf), to(#3465a4)); +} +.openerp ul.oe_form_status li.oe_active .label, .openerp ul.oe_form_status_clickable li.oe_active .label { + color: white; + text-shadow: 0 1px 1px #729fcf, 0 -1px 1px #3465a4; +} +.openerp ul.oe_form_status_clickable li { + cursor: pointer; +} +.openerp ul.oe_form_status_clickable li:hover { + background-color: #e8e8e8; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#cacaca)); + background-image: -webkit-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -moz-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -ms-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -o-linear-gradient(top, #e8e8e8, #cacaca); + background-image: linear-gradient(to bottom, #e8e8e8, #cacaca); +} +.openerp ul.oe_form_status_clickable li:hover .label { + text-shadow: 0 -1px 1px #fcfcfc, 0 1px 1px #dedede; +} +.openerp ul.oe_form_status_clickable li:hover .arrow span { + background-color: #e8e8e8; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#cacaca)); + background-image: -webkit-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -moz-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -ms-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -o-linear-gradient(top, #e8e8e8, #cacaca); + background-image: linear-gradient(to bottom, #e8e8e8, #cacaca); +} +.openerp ul.oe_form_status_clickable li .label { + color: #7c7bad; +} +.openerp ul.oe_form_status_clickable li.oe_active:hover { + background-color: #4c85c2; + background-image: -webkit-gradient(linear, left top, left bottom, from(#4c85c2), to(#284d7d)); + background-image: -webkit-linear-gradient(top, #4c85c2, #284d7d); + background-image: -moz-linear-gradient(top, #4c85c2, #284d7d); + background-image: -ms-linear-gradient(top, #4c85c2, #284d7d); + background-image: -o-linear-gradient(top, #4c85c2, #284d7d); + background-image: linear-gradient(to bottom, #4c85c2, #284d7d); +} +.openerp ul.oe_form_status_clickable li.oe_active:hover .label { + text-shadow: 0 -1px 1px #729fcf, 0 1px 1px #3465a4; +} +.openerp ul.oe_form_status_clickable li.oe_active:hover .arrow span { + background-color: #284d7d; + background: -moz-linear-gradient(135deg, #284d7d, #4c85c2); + background: -o-linear-gradient(135deg, #4c85c2, #284d7d); + background: -webkit-gradient(linear, left top, right bottom, from(#4c85c2), to(#284d7d)); } .openerp .oe_form .oe_form_field_one2many > .oe_view_manager .oe_list_pager_single_page { display: none; } +.openerp .oe_form_field_one2many > .oe_view_manager .oe_list_pager_single_page, .openerp .oe_form_field_many2many > .oe_view_manager .oe_list_pager_single_page { + display: none !important; +} +.openerp .oe_form_field_one2many .oe_form_field_one2many_list_row_add, .openerp .oe_form_field_many2many .oe_form_field_one2many_list_row_add { + font-weight: bold; +} .openerp .oe_form_field_one2many .oe_list_content > thead, .openerp .oe_form_field_many2many .oe_list_content > thead { border-bottom: 1px; } @@ -2322,13 +2542,58 @@ .openerp .oe_form_field_one2many .oe_list_buttons.oe_editing .oe_list_save, .openerp .oe_form_field_many2many .oe_list_buttons.oe_editing .oe_list_save { visibility: hidden; } +.openerp .oe_form_editable .oe_list_editable .oe_list_content td.oe_required { + background-color: #d2d2ff; +} +.openerp .oe_form_editable .oe_list_editable .oe_list_content td.oe_readonly { + background-color: #eeeeee; +} +.openerp .oe_list_editable .oe_list_content td.oe_list_field_cell { + padding: 4px 6px 3px 6px; +} +.openerp .oe_list.oe_list_editable td.oe_list_record_delete { + position: absolute; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_drop_down_button { + top: 5px; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_cm_button { + display: none; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea { + height: 27px; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea { + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + border: 1px solid #aaaaff; + margin: 0; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input { + text-align: right; + width: 100%; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime > span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date > span { + width: 100%; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master { + width: 100% !important; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_field_float, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_view_integer, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date { + min-width: 0 !important; + max-width: none !important; +} .openerp .oe_form .oe_form_field_many2many > .oe_list .oe_list_pager_single_page { display: none; } +.openerp .oe_list_buttons .oe_alternative { + visibility: hidden; +} .openerp .oe_list_buttons .oe_list_save, .openerp .oe_list_buttons .oe_list_discard { display: none; } -.openerp .oe_list_buttons.oe_editing .oe_list_add, .openerp .oe_list_buttons.oe_editing .oe_list_button_import { +.openerp .oe_list_buttons.oe_editing .oe_list_add { display: none; } .openerp .oe_list_buttons.oe_editing .oe_list_save { @@ -2337,15 +2602,32 @@ .openerp .oe_list_buttons.oe_editing .oe_list_discard { display: inline; } +.openerp .oe_list_buttons.oe_editing .oe_alternative { + visibility: visible; +} .openerp .oe_list { position: relative; } +.openerp .oe_list .oe_form .oe_form_nosheet { + margin: 0; + padding: 0; + border: none; +} .openerp .oe_list .oe_form .oe_form_field { width: auto; position: absolute; margin: 0 !important; padding: 0; } +.openerp .oe_list .oe_list_content .oe_group_header { + background-color: #fcfcfc; + 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 .oe_list_content { width: 100%; } @@ -2399,6 +2681,7 @@ cursor: pointer; } .openerp .oe_list_content > tbody > tr { + height: 27px; border-top: 1px solid #dddddd; } .openerp .oe_list_content > tbody > tr > td.oe_list_field_cell { @@ -2435,15 +2718,6 @@ background: #eeeeee; font-weight: bold; } -.openerp .oe_list_content > tbody tr:hover td, .openerp .oe_list_content tbody tr:hover th { - background-color: #eeeeee; - background-image: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#dedede)); - background-image: -webkit-linear-gradient(top, #eeeeee, #dedede); - background-image: -moz-linear-gradient(top, #eeeeee, #dedede); - background-image: -ms-linear-gradient(top, #eeeeee, #dedede); - background-image: -o-linear-gradient(top, #eeeeee, #dedede); - background-image: linear-gradient(to bottom, #eeeeee, #dedede); -} .openerp .oe_list_content .numeric { text-align: right; width: 82px; @@ -2451,6 +2725,35 @@ .openerp .oe_list_content .numeric input { text-align: right; } +.openerp .oe_list_content th.oe_list_header_handle { + font-size: 1px; + overflow: hidden; + text-indent: -9001px; +} +.openerp .oe_list_content td.oe_list_field_handle { + width: 1em; + padding: 0 !important; + cursor: ns-resize; +} +.openerp .oe_list_content td.oe_list_field_handle .oe_list_handle { + font-size: 1px; + letter-spacing: -1px; + color: transparent; + font-weight: normal; + margin-right: 7px; +} +.openerp .oe_list_content td.oe_list_field_handle .oe_list_handle:before { + font: 18px "entypoRegular"; + content: "}"; + color: #e0e0e0; +} +.openerp .oe_list_cannot_edit .oe_list_header_handle, .openerp .oe_list_cannot_edit .oe_list_field_handle { + display: none !important; + padding: 0 !important; +} +.openerp .oe_list_cannot_delete .oe_list_record_delete { + display: none !important; +} .openerp .tree_header { background-color: #f0f0f0; border-bottom: 1px solid #cacaca; @@ -2467,27 +2770,33 @@ width: 100%; background-color: white; border-spacing: 0; + color: #4c4c4c; } .openerp .oe-treeview-table th { padding: 10px; - color: #4c4c4c; font-weight: bold; background-color: #f0f0f0; border-bottom: 2px solid #cacaca; } -.openerp .oe-treeview-table .treeview-tr, .openerp .oe-treeview-table .treeview-td { +.openerp .oe-treeview-table td { cursor: pointer; - border-right: 1px dotted #afafb6; - vertical-align: top; + vertical-align: middle; text-align: left; - border-bottom: 1px solid #cfcccc; -} -.openerp .oe-treeview-table tr:hover { - background-color: #e0e0f8; + vertical-align: middle; + height: 20px; + padding-left: 4px; + padding-right: 4px; + border-right: 1px solid #e7e7e7; } -.openerp .oe-treeview-table .oe-number { +.openerp .oe-treeview-table td.oe_number { text-align: right !important; } +.openerp .oe-treeview-table tr { + border-bottom: 1px solid #d6d6d6; +} +.openerp .oe-treeview-table tr:hover { + background-color: #e7e7e7; +} .openerp .oe-treeview-table span { font-size: 90%; font-weight: normal; @@ -2503,9 +2812,6 @@ .openerp .oe-treeview-table .treeview-tr.oe-treeview-first span, .openerp .oe-treeview-table .treeview-td.oe-treeview-first span { margin-left: 16px; } -.openerp .oe_trad_field.touched { - border: 1px solid green !important; -} .openerp .oe_layout_debugging .oe_form_group { outline: 2px dashed green; } @@ -2555,3 +2861,48 @@ .kitten-mode-activated > * { opacity: 0.7; } + +div.ui-widget-overlay { + background: black; + filter: alpha(opacity=30); + opacity: 0.3; +} + +.ui-widget { + font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; + color: #4c4c4c; + font-size: 13px; +} + +.ui-menu { + padding: 2px 0; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + margin-top: 4px; + border: 1px solid #afafb6; +} +.ui-menu .ui-menu-item { + width: 100%; + padding: 0; +} +.ui-menu .ui-menu-item a { + padding: 1px 16px; +} +.ui-menu .ui-menu-item a.ui-corner-all { + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} +.ui-menu .ui-menu-item a.ui-state-active { + background: #f0f0fa; +} +.ui-menu .ui-menu-item a.ui-state-hover, .ui-menu .ui-menu-item a.ui-state-active { + background: #7c7bad; +} + +.ui-corner-all { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 688c306169b..5a676deae2e 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1,11 +1,14 @@ @charset "utf-8" // Variables {{{ -$section-title-color: #8786b7 $facets-border: #afafb6 -$facets-border-selected: #a6a6fe +$section-title-color: #7C7BAD +$tag-bg-light: #f0f0fa +$tag-bg-dark: #7C7BAD +$tag-border: #afafb6 +$tag-border-selected: #a6a6fe $hover-background: #f0f0fa -$colour4: #8a89ba +$link-color: #7C7BAD $sheet-max-width: 860px // }}} // Mixins {{{ @@ -33,7 +36,7 @@ $sheet-max-width: 860px padding: 0 margin: 0 background: none - @include radius(none) + @include radius(0) @include box-shadow(none) @mixin vertical-gradient($startColor: #555, $endColor: #333) @@ -85,52 +88,69 @@ $sheet-max-width: 860px -ms-box-sizing: #{$type}-box box-sizing: #{$type}-box +@mixin skew-gradient($startColor: #555, $endColor: #333) + background-color: $endColor + background: -moz-linear-gradient(135deg, $endColor, $startColor) + background: -o-linear-gradient(135deg, $startColor, $endColor) + background: -webkit-gradient(linear, left top, right bottom, from($startColor), to($endColor)) + +@mixin transform($transform) + -webkit-transform: $transform + -moz-transform: $transform + -ms-transform: $transform + -o-transform: $transform + transform: $transform + +@mixin keyframes($name) + @-webkit-keyframes #{$name} + @content + @-moz-keyframes #{$name} + @content + @-ms-keyframes #{$name} + @content + @-o-keyframes #{$name} + @content + @keyframes #{$name} + @content + + // Transforms the (readable) text of an inline element into an mmlicons icon, // allows for actual readable text in-code (and in readers?) with iconic looks @mixin text-to-icon($icon-name, $color: #404040) font-size: 1px letter-spacing: -1px color: transparent + font-weight: normal &:before font: 21px "mnmliconsRegular" content: $icon-name color: $color +@mixin text-to-entypo-icon($icon-name, $color: #404040, $size: 21px) + font-size: 1px + letter-spacing: -1px + color: transparent + font-weight: normal + &:before + font: $size "entypoRegular" + content: $icon-name + color: $color + // }}} -// CSS animation bounces {{{ -@-moz-keyframes bounce - 0% - -moz-transform: scale(0) - opacity: 0 - 50% - -moz-transform: scale(1.3) - opacity: 0.4 - 75% - -moz-transform: scale(0.9) - opacity: 0.7 - 100% - -moz-transform: scale(1) - opacity: 1 - -@-webkit-keyframes bounce - 0% - -webkit-transform: scale(0) - opacity: 0 - 50% - -webkit-transform: scale(1.3) - opacity: 0.4 - 75% - -webkit-transform: scale(0.9) - opacity: 0.7 - 100% - -webkit-transform: scale(1) - opacity: 1 -// }}} + +@media print + .oe_topbar, .oe_leftbar, .oe_loading + display: none !important .openerp.openerp_webclient_container height: 100% position: relative +// jQueryUI css bug fixing +.text-tag .text-button + height: auto !important + min-height: 16px + .openerp // Global style {{{ padding: 0 @@ -139,18 +159,20 @@ $sheet-max-width: 860px color: #4c4c4c font-size: 13px background: white + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5) // }}} //Placeholder style{{{ \:-moz-placeholder - color: $facets-border !important + color: $tag-border !important font-style: italic !important \::-webkit-input-placeholder - color: $facets-border !important + color: $tag-border !important font-style: italic !important //}}} // Tag reset {{{ a text-decoration: none + cursor: pointer !important table padding: 0 border-collapse: collapse @@ -158,7 +180,7 @@ $sheet-max-width: 860px font-weight: bold background-color: #f0f0f0 th - border-right: 1px dotted $facets-border + border-right: 1px dotted $tag-border &:last-child border-right: none th, td @@ -174,11 +196,6 @@ $sheet-max-width: 860px @include vertical-gradient(#f0f0fa, #eeeef6) .zebra tbody tr:hover td @include vertical-gradient(#eee, #dedede) - ul, li, ol - margin: 0 - padding: 0 - li - list-style-type: none input, textarea, select padding: 2px 4px border: 1px solid #ccc @@ -190,10 +207,10 @@ $sheet-max-width: 860px margin: 4px 0 // }}} // Button style {{{ - a.button:link, a.button:visited, button, input[type='submit'] + a.button:link, a.button:visited, button, input[type='submit'], .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button display: inline-block - border: 1px solid #ababab - color: #404040 + border: 1px solid rgba(0,0,0,0.4) + color: #4c4c4c margin: 0 padding: 3px 12px font-size: 13px @@ -205,16 +222,18 @@ $sheet-max-width: 860px -webkit-font-smoothing: antialiased outline: none - a.button:hover, button:hover, input[type='submit']:hover + a.button:hover, button:hover, input[type='submit']:hover, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover @include vertical-gradient(#f6f6f6, #e3e3e3) cursor: pointer + background-position: 0 - a.button:focus, button:focus, input[type='submit']:focus + a.button:focus, button:focus, input[type='submit']:focus, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-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, button.active, input[type='submit']:active, input[type='submit'].active + a.button:active, a.button.active, button:active, button.active, input[type='submit']:active, input[type='submit'].active, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active @include vertical-gradient(#e3e3e3, #f6f6f6) @include box-shadow(none) @@ -227,19 +246,11 @@ $sheet-max-width: 860px text-shadow: 0 1px 1px #fff !important // }}} // Jquery ui Overrides {{{ - .ui-widget - font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif - font-size: 13px - .ui-menu - .ui-menu-item - margin: 0 8px 0 0 - padding: 0 0 0 12px - width: auto - a.ui-state-active - background: #f0f0fa - div.ui-widget-overlay - background: black - @include opacity(0.3) + .ui-widget-content a + color: $link-color + .oe_bounce_container + display: inline-block + // Modal box &.ui-dialog display: none @@ -273,9 +284,11 @@ $sheet-max-width: 860px margin: 0 @include radius(0 0 2px 2px) button - margin: 0 + margin: 0 4px 0 0 .ui-dialog-buttonset float: left + .ui-button + margin-right: 4px .ui-dialog-titlebar-close padding: 0 .ui-icon-closethick @@ -299,9 +312,11 @@ $sheet-max-width: 860px background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) @include radius(0 0 2px 2px) a - color: #8A89BA + color: $link-color &:hover text-decoration: underline + &:focus + outline: none .oe_logo margin-left: -6px .oe_bottom @@ -365,15 +380,15 @@ $sheet-max-width: 860px color: white background: #DC5F59 button.oe_highlight - @include vertical-gradient(#dc5f59, #b33630) - @include box-shadow(none) - button.oe_highlight:active - @include vertical-gradient(#b33630, #dc5f59) - @include box-shadow(none) + @include vertical-gradient(#df3f3f, #a21a1a) + @include box-shadow((0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset)) button.oe_highlight:hover - @include vertical-gradient(#DF6B66, #BF3A33) - //@include vertical-gradient(lighten(#dc5f59, 3%), lighten(#b33630, 3%)) - @include box-shadow(0 0 1px rgba(0, 0, 0, 0.2)) + @include vertical-gradient(lighten(#e04f4f, 2%), lighten(#a21a1a, 2%)) + button.oe_highlight:active + @include vertical-gradient(lighten(#a21a1a, 8%), darken(#df3f3f, 8%)) + .oe_background_grey + background: #eee !important + .oe_form_dirty .oe_highlight_on_dirty color: white @@ -399,22 +414,28 @@ $sheet-max-width: 860px button margin: 4px .oe_avatar - margin: 0 6px 15px 0 > img - height: 90px - max-width: 100px + max-height: 90px + max-width: 90px @include radius(3px) - @include box-shadow(0 1px 3px rgba(0, 0, 0, 0.3)) + @include box-shadow(0 1px 4px rgba(0, 0, 0, 0.4)) border: none + margin-bottom: 10px .oe_avatar + div margin-left: 5px + .oe_image_small > img + max-width: 50px + max-height: 50px + .oe_image_medium > img + max-width: 180px + max-height: 180px .oe_button.oe_link @include reset() img display: none span @include reset() - color: #8A89BA + color: $link-color font-weight: bold &:hover text-decoration: underline @@ -429,22 +450,39 @@ $sheet-max-width: 860px text-decoration: none .oe_star_on color: gold - .oe_bounce - -moz-animation: bounce .40s linear - -webkit-animation: bounce .40s linear // }}} + + // Generic blocks {{{ + header + position: relative + border-bottom: 1px solid #cacaca + padding-left: 2px + @include vertical-gradient(#fcfcfc, #dedede) + > span + margin-left: 4px + ul + display: inline-block + float: right + .oe_button + margin: 3px 2px 1px + // }}} + // Tags (for many2many tags, among others) {{{ .oe_tag - border-radius: 2px - @include box-sizing(border) - border: 1px solid #9DACCC - background: #E2E6F0 - color: black !important - padding: 0px 3px 0px 3px + border: 1px solid $tag-border + font-size: 11px + padding: 2px 4px margin: 0 2px 2px 0 - height: 16px - // font: 11px "lucida grande", tahoma, verdana, arial, sans-serif + @include radius(3px) + background: $tag-bg-light + color: #4C4C4C + .oe_tag_dark + background: $tag-bg-dark + color: #eee + .oe_tags + &.oe_inline + min-width: 250px .text-wrap width: 100% !important textarea @@ -452,6 +490,7 @@ $sheet-max-width: 860px .text-core .text-wrap .text-dropdown .text-list .text-suggestion em font-style: italic text-decoration: none + margin-bottom: 1px // }}} // Tooltips {{{ &.oe_tooltip @@ -469,6 +508,16 @@ $sheet-max-width: 860px 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 @@ -522,7 +571,7 @@ $sheet-max-width: 860px position: relative cursor: pointer .oe_dropdown_toggle - color: #404040 + color: rgba(0,0,0,0.5) font-weight: normal .oe_dropdown_hover:hover .oe_dropdown_menu, .oe_dropdown_menu.oe_opened display: block @@ -531,8 +580,10 @@ $sheet-max-width: 860px position: absolute top: 26px left: 0 - z-index: 1 - border: 1px solid $facets-border + z-index: 3 + margin: 0 + padding: 0 + border: 1px solid $tag-border background: white padding: 4px 0 min-width: 140px @@ -547,6 +598,7 @@ $sheet-max-width: 860px float: none display: block position: relative + margin: 0 padding: 2px 8px > a white-space: nowrap @@ -563,7 +615,8 @@ $sheet-max-width: 860px text-indent: -99999px vertical-align: top margin-top: 8px - margin-left: 4px + //margin-left set at 3px to avoid a strange overflow + margin-left: 3px border-left: 4px solid transparent border-right: 4px solid transparent border-top: 4px solid#404040 @@ -635,9 +688,10 @@ $sheet-max-width: 860px text-align: center font-size: 14px height: 100% - li + ul, li + margin: 0 list-style-type: none - padding-bottom: 4px + padding: 0 0 4px 0 button float: right display: inline-block @@ -654,7 +708,7 @@ $sheet-max-width: 860px font-family: "Lucida Grande", Helvetica, Verdana, Arial border: 1px solid #999999 background: whitesmoke - @include box-shadow(inset 0 1px 4px rgba(0, 0, 0, 0.3)) + @include box-shadow(inset 0 1px 4px rgba(0, 0, 0, 0.4)) @include radius(3px) input margin-bottom: 9px @@ -754,29 +808,28 @@ $sheet-max-width: 860px .oe_topbar, .oe_leftbar display: none // }}} - // WebClient.topbar {{{ + // Top Menu {{{ + .oe_topbar width: 100% - height: 31px - border-top: solid 1px #d3d3d3 + height: 32px + background-color: #414141 @include vertical-gradient(#646060, #262626) .oe_topbar_item display: block padding: 5px 10px 7px line-height: 20px height: 20px + text-decoration: none color: #eee vertical-align: top text-shadow: 0 1px 1px rgba(0,0,0,0.2) - &:hover - background: #303030 + @include transition(all 0.2s ease-out) + &:hover, + background: rgba(0,0,0,0.2) + text-shadow: black 0px 0px 3px color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) - .oe_active - background: #303030 - font-weight: bold - color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) + @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) .oe_topbar_avatar width: 24px @@ -806,13 +859,65 @@ $sheet-max-width: 860px @include vertical-gradient(#292929, #191919) @include box-shadow(none) + // oe menu is the list of the buttons on the left side of the bar. + // So why aren't the buttons oe_topbar_items ? This sad state of affairs + // is a leftover from an uncomplete refactoring when the left and top menu + // were merged. You are welcome to refactor and clean this up + .oe_menu + float: left + padding: 0 + margin: 0 + font-size: 13px + > li + list-style-type: none + padding: 0 + margin: 0 + float: left + display: block + color: #eeeeee + > a + display: block + padding: 5px 10px 7px + line-height: 20px + height: 20px + text-decoration: none + color: #eee + vertical-align: top + text-shadow: 0 1px 1px rgba(0,0,0,0.2) + @include transition(all 0.2s ease-out) + &:hover, + background: rgba(0,0,0,0.2) + text-shadow: black 0px 0px 3px + color: white + @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) + > .oe_active + background: rgba(0,0,0,0.30) + text-shadow: black 0px 0px 3px + @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) + + .oe_user_menu + float: right + padding: 0 + margin: 0 + li + list-style-type: none + float: left + .oe_dropdown_menu + right: -1px + + .oe_systray > div + float: left + padding: 0 4px 0 4px + .oe_systray + float: right + // }}} // Webclient.leftbar {{{ .oe_leftbar display: none width: 220px background: #f0eeee - border-right: 1px solid $facets-border + border-right: 1px solid $tag-border text-shadow: 0 1px 1px white padding-bottom: 16px a.oe_logo @@ -841,60 +946,22 @@ $sheet-max-width: 860px color: #c81010 font-style: italic // }}} - // UserMenu {{{ - .oe_user_menu - float: right - padding: 0 - margin: 0 - li - list-style-type: none - float: left - .oe_dropdown_menu - right: -1px - // }}} - // Systray {{{ - .oe_systray > div - float: left - padding: 0 4px 0 4px - .oe_systray - float: right - // }}} - // Menu {{{ - .oe_menu - float: left - padding: 0 - margin: 0 - li - float: left - a - display: block - padding: 5px 10px 7px - line-height: 20px - height: 20px - color: #eee - vertical-align: top - text-shadow: 0 1px 1px rgba(0,0,0,0.2) - &:hover - background: #303030 - color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) - .oe_active - background: #303030 - font-weight: bold - color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) + // Webclient.leftbar items {{{ + .oe_secondary_menu_section font-weight: bold margin-left: 8px - color: $colour4 + color: $link-color .oe_secondary_submenu padding: 2px 0 8px 0 margin: 0 width: 100% display: inline-block - > li + li position: relative - padding: 1px 0 1px 20px + margin: 0 + padding: 1px 0 1px 20px !important + list-style-type: none a display: block color: #4c4c4c @@ -904,7 +971,7 @@ $sheet-max-width: 860px top: 1px right: 1px font-size: 10px - background: $colour4 + background: $link-color color: white padding: 2px 4px margin: 1px 6px 0 0 @@ -914,30 +981,30 @@ $sheet-max-width: 860px @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2)) .oe_menu_counter float: right - background: #8a89ba - color: #eee - font-size: 10px - border: 1px solid lightgray - padding: 2px 4px text-shadow: 0 1px 1px rgba(0,0,0,0.2) - margin: 0px - @include radius(4px) + margin: 1px + padding: 1px 4px + border: none + &:hover + cursor: pointer + @include transform(scale(1.1)) .oe_active - background: $colour4 border-top: 1px solid lightGray - border-bottom: 1px solid lightGray + border-bottom: 1px solid #dedede text-shadow: 0 1px 1px rgba(0,0,0,0.2) - @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2)) + @include box-shadow((inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2))) + background: $link-color a color: white .oe_menu_label background: #eee - color: $colour4 + color: $link-color text-shadow: 0 1px 1px white @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2)) .oe_menu_counter background: #eee - color: #8a89ba + color: $link-color + @include box-shadow(inset 0 1px 2px rgba(0, 0, 0, 0.2)) .oe_menu_toggler:before width: 0 height: 0 @@ -959,21 +1026,55 @@ $sheet-max-width: 860px border-left: 4px solid transparent border-right: 4px solid transparent border-top: 4px solid #4c4c4c + + // }}} + // About openerp {{{ + .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 // }}} // ActionManager {{{ .oe_application width: 100% a - color: $colour4 + color: $link-color &:hover text-decoration: underline > div height: 100% + .oe_breadcrumb_item:not(:last-child) + display: inline-block + max-width: 7em + white-space: nowrap + overflow: hidden + text-overflow: ellipsis // }}} // ViewManager common {{{ .oe_view_manager .oe_view_manager_body - height: inherit + height: inherit .oe_view_manager_view_kanban height: inherit @@ -988,6 +1089,9 @@ $sheet-max-width: 860px .oe_header_row:last-child td padding-top: 0 + .oe_header_row:first-child + td + padding-top: 8px .oe_view_manager_sidebar margin: 0px auto text-align: center @@ -998,7 +1102,9 @@ $sheet-max-width: 860px margin: 0 float: left a - color: $colour4 + color: $link-color + .oe_dropdown_menu + line-height: normal .oe_button_group display: inline-block border: 1px solid #ababab @@ -1029,6 +1135,9 @@ $sheet-max-width: 860px .oe_pager_value float: left margin-right: 8px + ul.oe_pager_group + padding: 0 + margin: 0 .oe_pager_group float: left height: 24px @@ -1041,6 +1150,8 @@ $sheet-max-width: 860px height: 24px line-height: 24px padding: 0 + margin: 0 + list-style-type: none float: left border-right: 1px solid #ababab &:last-child @@ -1059,11 +1170,16 @@ $sheet-max-width: 860px // }}} // ViewManager.switches {{{ .oe_view_manager_switch + padding: 0 + margin: 0 0 0 8px li - text-align: center + margin: 0 width: 24px height: 24px line-height: 16px + padding: 0 + text-align: center + list-style-type: none a position: relative .oe_vm_switch_list:after, .oe_vm_switch_tree:after @@ -1096,7 +1212,6 @@ $sheet-max-width: 860px .oe_view_manager_current height: 100% > .oe_view_manager_header - border-top: 1px solid #cacaca border-bottom: 1px solid #cacaca @include vertical-gradient(#fcfcfc, #dedede) @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) @@ -1108,7 +1223,7 @@ $sheet-max-width: 860px padding-top: 8px // }}} // ViewManager inline {{{ - .oe_view_manager_inline + .oe_view_manager_inline, .oe_view_manager_inlineview height: 100% > .oe_view_manager_header display: none @@ -1135,8 +1250,8 @@ $sheet-max-width: 860px font-size: 12px line-height: 18px &.oe_focused - border-color: $facets-border-selected - @include box-shadow(0 1px 2px $facets-border-selected inset) + border-color: $tag-border-selected + @include box-shadow(0 1px 2px $tag-border-selected inset) .oe_searchview_clear cursor: pointer position: absolute @@ -1187,7 +1302,7 @@ $sheet-max-width: 860px vertical-align: top display: inline-block line-height: 17px - .oe_searchview_input, .oe_searchview_facet + .oe_searchview_facet height: 18px margin: 1px 0 font-size: 11px @@ -1195,17 +1310,20 @@ $sheet-max-width: 860px outline: none .oe_searchview_input padding: 0 0 0 6px + font-size: 12px + height: 16px + margin-top: 3px + &:focus + outline: none .oe_searchview_facet position: relative cursor: pointer - border: 1px solid $facets-border - @include radius(3px) - background: #8786b7 + padding: 0 -webkit-font-smoothing: auto // spacing for opera, FF &:focus - border-color: $facets-border-selected - @include box-shadow(0 0 3px 1px $facets-border-selected) + border-color: $tag-border-selected + @include box-shadow(0 0 3px 1px $tag-border-selected) .oe_facet_values background: #f0f0fa @include radius(0 3px 3px 0) @@ -1218,8 +1336,9 @@ $sheet-max-width: 860px .oe_facet_category.oe_i font-size: 16px .oe_facet_value - border-left: 1px solid $facets-border + border-left: 1px solid $tag-border text-shadow: 0 1px 1px white + color: #4C4C4C &:last-child padding-right: 16px .oe_facet_remove @@ -1230,6 +1349,7 @@ $sheet-max-width: 860px line-height: 8px width: 12px height: 12px + padding-top: 1px text-align: center font-weight: bold cursor: pointer @@ -1253,7 +1373,7 @@ $sheet-max-width: 860px background-color: white min-width: 100% display: none - border: 1px solid $facets-border + border: 1px solid $tag-border text-align: left @include radius(4px) @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) @@ -1361,6 +1481,11 @@ $sheet-max-width: 860px list-style: none margin: 0 white-space: nowrap + &:first-child .searchview_extended_prop_or + visibility: hidden + .searchview_extended_prop_or + opacity: 0.5 + margin-left: -14px .oe_opened h4:before content: "â–¾ " @@ -1459,104 +1584,81 @@ $sheet-max-width: 860px display: none !important .oe_form .oe_form_field_date width: auto + // Sheet and padding .oe_form_nosheet - margin: 20px - .oe_form_nosheet > header - margin-top: -20px - margin-left: -20px - margin-right: -20px + margin: 16px + > header + margin: -16px -16px 0 -16px + padding: 0 + .oe_form_sheetbg + padding: 16px 0 + .oe_form_sheet_width + min-width: 650px + max-width: $sheet-max-width + margin: 0 auto + .oe_form_sheet + background: white + min-height: 330px + padding: 16px + // Sheet inline mode + .oe_application + .oe_form_sheetbg + background: url(/web/static/src/img/form_sheetbg.png) + border-bottom: 1px solid #ddd + .oe_form_sheet + border: 1px solid #c8c8d3 //$tag-border + @include box-shadow(0 4px 20px rgba(0,0,0,0.15)) + .ui-tabs + margin: 0 -16px + .oe_notebook_page + padding: 0 16px // }}} // FormView.custom tags and classes {{{ - .oe_form - header - position: relative - border-bottom: 1px solid #cacaca - @include vertical-gradient(#fcfcfc, #dedede) - padding: 0 8px - line-height: 30px - ul - display: inline-block - float: right + .oe_form header .oe_tags + margin: 5px 0 0 5px + width: 400px + padding-bottom: 0 div.oe_chatter min-width: 650px max-width: $sheet-max-width margin: 0 auto + padding: 16px 0 48px + .oe_grey + color: #aaa + max-width: 650px + margin: 0 0 10px 0 div.oe_form_configuration - div.oe_horizontal_separator - margin: 25px 0 10px 0 - p + p, ul, ol color: #aaa max-width: 650px label min-width: 150px - ul.oe_form_steps - height: 30px - padding: 0 - margin: 0 - text-shadow: 0 1px 1px white - img - vertical-align: top - margin-left: 8px - li - border-right: none - padding: 0 - margin: 0 - float: left - vertical-align: top - height: 30px - padding: 0 0 0 12px - &:first-child - border-left: 1px solid #cacaca - &:last-child - margin-right: 12px - padding-right: 12px - border-right: 1px solid #cacaca - a - color: #4c4c4c - &:hover - color: black - .oe_form_steps_active - font-weight: bold - color: #b33630 - .oe_form .oe_subtotal_footer - width: 1% !important - td.oe_form_group_cell - text-align: right - padding: 0 !important - td.oe_form_group_cell_label - border-right: none - .oe_subtotal_footer_separator - width: 108px - border-top: 1px solid #cacaca - font-weight: bold - font-size: 18px - label:after - content: ":" - label.oe_subtotal_footer_separator - font-weight: bold !important - padding: 2px 11px 2px 0px !important - label.oe_form_label_help - font-weight: normal - // no sheet in popups - .oe_application - .oe_form_sheetbg - background: url(/web/static/src/img/form_sheetbg.png) - padding: 8px 0 - border-bottom: 1px solid #ddd - .oe_form_sheet_width - min-width: 650px - max-width: $sheet-max-width - margin: 0 auto - .oe_form_sheet - background: white - min-height: 330px - padding: 16px - border: 1px solid $facets-border - @include box-shadow(0 0 10px rgba(0,0,0,0.3)) - .ui-tabs - margin: 0 -16px - .oe_notebook_page - padding: 0 16px + .oe_form_group_cell_label + padding: 1px 0 + .oe_form_group_cell div div + padding: 1px 0 + .oe_subtotal_footer + width: 1% !important + td.oe_form_group_cell + text-align: right + padding: 0 !important + td.oe_form_group_cell_label + border-right: none + .oe_subtotal_footer_separator + width: 108px + border-top: 1px solid #cacaca + margin-top: 4px + padding-top: 4px + font-weight: bold + font-size: 18px + label:after + content: ":" + label.oe_subtotal_footer_separator + font-weight: bold !important + padding: 2px 11px 2px 0px !important + label.oe_form_label_help + font-weight: normal + // }}} // FormView.group {{{ .oe_form @@ -1564,13 +1666,13 @@ $sheet-max-width: 860px margin: 2px td.oe_form_group_cell_label border-right: 1px solid #ddd - padding: 4px 0px 4px 0px + padding: 2px 0px label line-height: 18px display: block - min-width: 120px + min-width: 140px td.oe_form_group_cell + .oe_form_group_cell - padding-left: 6px + padding: 2px 0 2px 8px .oe_form_group width: 100% margin: 9px 0 9px 0 @@ -1582,7 +1684,7 @@ $sheet-max-width: 860px .oe_form_label_help[for], .oe_form_label[for] font-weight: bold white-space: nowrap - padding-right: 6px + padding-right: 8px span font-size: 80% color: darkGreen @@ -1597,8 +1699,8 @@ $sheet-max-width: 860px .oe_horizontal_separator font-weight: bold font-size: 20px - margin: 8px 0px 8px 0px - color: #53637e + margin: 15px 0px 10px 0px + color: $section-title-color .oe_horizontal_separator:empty height: 5px .oe_vertical_separator @@ -1645,7 +1747,6 @@ $sheet-max-width: 860px width: 100% display: inline-block padding: 2px 2px 2px 0px - line-height: 18px input margin: 0px input[type="text"], input[type="password"], input[type="file"], select @@ -1654,17 +1755,18 @@ $sheet-max-width: 860px input[type="text"], input[type="password"], input[type="file"], select, textarea @include box-sizing(border) background: white - min-width: 70px + min-width: 60px color: #1f1f1f input[readonly], select[readonly], textarea[readonly], input[disabled], select[disabled] background: #E5E5E5 !important color: #666 textarea[disabled] border: none - border-left: 8px solid #eee padding-left: 8px @include box-shadow(none) @include radius(0px) + textarea.oe_inline[disabled] + border-left: 8px solid #eee .oe_form_field_url button img vertical-align: top .oe_form_field_date, @@ -1677,8 +1779,8 @@ $sheet-max-width: 860px display: none .oe_datepicker_root display: inline-block - .oe_form_required - input, select, textarea + .oe_form_required + input:not([disabled]), select:not([disabled]), textarea:not([disabled]) background-color: #D2D2FF !important .oe_form_invalid input, select, textarea @@ -1709,12 +1811,20 @@ $sheet-max-width: 860px > div position: relative overflow: hidden + .oe_form_field_html + position: relative + width: 600px + margin-left: 130px + margin-top: 32px + margin-bottom: 32px + text-align: justify + .oe_form_editable .oe_form .oe_form_field_integer - width: 7em !important + width: 6em !important .oe_form_field_float - width: 8em !important + width: 7em !important .oe_form_field_date width: 7.5em !important .oe_form_field_datetime @@ -1725,19 +1835,17 @@ $sheet-max-width: 860px * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image */ .oe_hidden_input_file - overflow: hidden + // Position: relative is used for the hidden input[type=file] + // Do not remove it anymore ! position: relative - display: inline-block - width: 45px - height: 30px input.oe_form_binary_file z-index: 0 line-height: 0 font-size: 12px position: absolute - // Should be adjusted for all browsers top: 1px - right: 10px + left: 0 + right: 0 opacity: 0 filter: alpha(opacity = 0) -ms-filter: "alpha(opacity=0)" @@ -1752,15 +1860,14 @@ $sheet-max-width: 860px vertical-align: top .oe_form_field_image_controls position: absolute - white-space: nowrap top: 1px - padding: 3px 0 0 0 - margin: 0 1px + padding: 4px + width: 100% display: none - width: 99% text-align: center - background: url(/web/static/src/img/form_sheetbg.png) - border-bottom: 1px dotted black + color: #eee + background: rgba(37,37,37,0.9) + @include radius(3px 3px 0 0) @include box-sizing(border) &:hover .oe_form_field_image_controls display: block @@ -1775,6 +1882,86 @@ $sheet-max-width: 860px right: 0px .oe_m2o_cm_button line-height: 14px + float: right + padding-left: 2px + &.ui-autocomplete + li.oe_m2o_dropdown_option a + font-style: italic + padding-left: 2em + li:not(.oe_m2o_dropdown_option) + li.oe_m2o_dropdown_option + margin-top: 10px + // }}} + // FormView.fieldstatus {{{ + ul.oe_form_status, ul.oe_form_status_clickable + display: inline-block + margin: 0 + padding: 0 18px 0 0 + li + display: inline-block + list-style-type: none + // It is probably a bad idea to modify this margin + margin: 0 -18px 0 0 + padding: 0 + @include vertical-gradient(#fcfcfc, #dedede) + &:first-child .label + border-left: 1px solid #cacaca + padding-left: 14px + &:last-child + border-right: 1px solid #cacaca + .label + padding-right: 14px + .arrow + display: none + .label + color: #4c4c4c + text-shadow: 0 1px 1px #fcfcfc, 0 -1px 1px #dedede + padding: 7px + display: inline-block + padding-left: 24px + margin: 0 + position: relative + .arrow + width: 17px + display: inline-block + vertical-align: top + overflow: hidden + margin-left: -5px + span + position: relative + width: 24px + height: 24px + display: inline-block + margin-left: -12px + margin-top: 3px + box-shadow: -1px 1px 2px rgba(255,255,255,0.2), inset -1px 1px 1px rgba(0,0,0,0.2) + @include skew-gradient(#fcfcfc, #dedede) + @include radius(3px) + @include transform(rotate(45deg)) + li.oe_active + @include vertical-gradient(#729fcf, #3465a4) + .arrow span + @include skew-gradient(#729fcf, #3465a4) + .label + color: white + text-shadow: 0 1px 1px #729fcf, 0 -1px 1px #3465a4 + ul.oe_form_status_clickable + li + cursor: pointer + &:hover + @include vertical-gradient(darken(#fcfcfc, 8%), darken(#dedede, 8%)) + .label + text-shadow: 0 -1px 1px #fcfcfc, 0 1px 1px #dedede + .arrow span + @include vertical-gradient(darken(#fcfcfc, 8%), darken(#dedede, 8%)) + .label + color: $link-color + li.oe_active + &:hover + @include vertical-gradient(darken(#729fcf, 10%), darken(#3465a4, 10%)) + .label + text-shadow: 0 -1px 1px #729fcf, 0 1px 1px #3465a4 + .arrow span + @include skew-gradient(darken(#729fcf, 10%), darken(#3465a4, 10%)) // }}} // FormView.one2many {{{ .oe_form .oe_form_field_one2many > .oe_view_manager @@ -1782,6 +1969,11 @@ $sheet-max-width: 860px display: none .oe_form_field_one2many,.oe_form_field_many2many // TODO: oe_form_field_one2many_list? + > .oe_view_manager + .oe_list_pager_single_page + display: none !important + .oe_form_field_one2many_list_row_add + font-weight: bold .oe_list_content > thead border-bottom: 1px @@ -1811,6 +2003,46 @@ $sheet-max-width: 860px .oe_list_buttons.oe_editing .oe_list_save // keep "save row" button hidden in o2m visibility: hidden + .oe_form_editable + .oe_list_editable + .oe_list_content + td.oe_required + background-color: #d2d2ff + td.oe_readonly + background-color: #eee + + $row-height: 27px + .oe_list_editable + .oe_list_content + td.oe_list_field_cell + padding: 4px 6px 3px 6px + .oe_list.oe_list_editable + td.oe_list_record_delete + position: absolute + .oe_list.oe_list_editable.oe_editing + .oe_m2o_drop_down_button + top: 5px + .oe_m2o_cm_button + display: none + .oe_form_field + input, textarea + height: $row-height + input, textarea + @include radius(0) + border: 1px solid #aaf + margin: 0 + &.oe_form_field_float,&.oe_form_view_integer + input + text-align: right + width: 100% + &.oe_form_field_datetime,&.oe_form_field_date + > span + width: 100% + input.oe_datepicker_master + width: 100% !important + .oe_form_field_float,.oe_form_view_integer,&.oe_form_field_datetime,&.oe_form_field_date + min-width: 0 !important + max-width: none !important // }}} // FormView.many2many {{{ .oe_form .oe_form_field_many2many > .oe_list @@ -1819,23 +2051,34 @@ $sheet-max-width: 860px // }}} // ListView {{{ .oe_list_buttons + .oe_alternative + visibility: hidden .oe_list_save, .oe_list_discard display: none &.oe_editing - .oe_list_add, .oe_list_button_import + .oe_list_add display: none .oe_list_save display: inline-block .oe_list_discard display: inline + .oe_alternative + visibility: visible .oe_list position: relative - .oe_form .oe_form_field - width: auto - position: absolute - margin: 0 !important // dammit - padding: 0 + .oe_form + .oe_form_nosheet + margin: 0 // FIXME: either class or border should not be by default + padding: 0 + border: none + .oe_form_field + width: auto + position: absolute + margin: 0 !important // dammit + padding: 0 + .oe_list_content .oe_group_header + @include vertical-gradient(#fcfcfc, #dedede) .oe_list_content width: 100% @@ -1877,6 +2120,7 @@ $sheet-max-width: 860px > tbody cursor: pointer > tr + height: $row-height border-top: 1px solid #ddd > td.oe_list_field_cell padding: 3px 6px @@ -1894,20 +2138,39 @@ $sheet-max-width: 860px > tr:nth-child(odd) background-color: #f0f0fa @include vertical-gradient(#f0f0fa, #eeeef6) + > tfoot border-top: 2px solid #cacaca border-bottom: 1px solid #cacaca background: #eee font-weight: bold - > tbody tr:hover td, tbody tr:hover th - @include vertical-gradient(#eee, #dedede) .numeric text-align: right width: 82px input text-align: right + th.oe_list_header_handle + font-size: 1px + overflow: hidden + text-indent: -9001px + td.oe_list_field_handle + width: 1em + padding: 0 !important + cursor: ns-resize + .oe_list_handle + @include text-to-entypo-icon("}",#E0E0E0,18px) + margin-right: 7px + + .oe_list_cannot_edit + .oe_list_header_handle, .oe_list_field_handle + display: none !important + padding: 0 !important + .oe_list_cannot_delete + .oe_list_record_delete + display: none !important + // }}} // Tree view {{{ .tree_header @@ -1924,22 +2187,29 @@ $sheet-max-width: 860px width: 100% background-color: white border-spacing: 0 + color: #4c4c4c th padding: 10px - color: #4c4c4c font-weight: bold background-color: #f0f0f0 border-bottom: 2px solid #cacaca - .treeview-tr, .treeview-td + + td cursor: pointer - border-right: 1px dotted $facets-border - vertical-align: top + vertical-align: middle text-align: left - border-bottom: 1px solid #cfcccc - tr:hover - background-color: #e0e0f8 - .oe-number + vertical-align: middle + height: 20px + padding-left: 4px + padding-right: 4px + border-right: 1px solid #e7e7e7 + + td.oe_number text-align: right !important + tr + border-bottom: 1px solid #d6d6d6 + &:hover + background-color: #e7e7e7 span font-size: 90% font-weight: normal @@ -1951,10 +2221,7 @@ $sheet-max-width: 860px background-image: url(/web/static/src/img/collapse.gif) .treeview-tr.oe-treeview-first span, .treeview-td.oe-treeview-first span margin-left: 16px - // }}} - // Translation {{{ - .oe_trad_field.touched - border: 1px solid green !important + // }}} // Debugging stuff {{{ .oe_layout_debugging @@ -2000,6 +2267,37 @@ $sheet-max-width: 860px // }}} +// The jQuery-ui overlay and Autocomplete are outside the .openerp div, please don't add indentation !!! +div.ui-widget-overlay + background: black + @include opacity(0.3) + +.ui-widget + font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif + color: #4c4c4c + font-size: 13px + +.ui-menu + padding: 2px 0 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) + margin-top: 4px + border: 1px solid $tag-border + .ui-menu-item + width: 100% + padding: 0 + a + padding: 1px 16px + a.ui-corner-all + @include radius(0) + a.ui-state-active + background: #f0f0fa + .ui-menu-item a.ui-state-hover, .ui-menu-item a.ui-state-active + background: $tag-bg-dark + //color: #4c4c4c + //@include vertical-gradient(#f0f0fa, #eeeef6) + +.ui-corner-all + @include radius(3px) // au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css" // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: diff --git a/addons/web/static/src/css/data_import.css b/addons/web/static/src/css/data_import.css deleted file mode 100644 index 47cd52aa081..00000000000 --- a/addons/web/static/src/css/data_import.css +++ /dev/null @@ -1,46 +0,0 @@ -.openerp .oe_import_grid { - border: none; - border-collapse: collapse; -} -.openerp .oe_import_grid-header .oe_import_grid-cell { - background: url(../img/gradientlinebg.gif) repeat-x #CCCCCC; - border-bottom: 1px solid #E3E3E3; - font-weight: bold; - text-align: left; -} -.openerp .oe_import_grid-row .oe_import_grid-cell { - border-bottom: 1px solid #E3E3E3; -} - -.openerp .oe_import_no_result .oe_import_result { - display: none; -} -.openerp .oe_import fieldset { - cursor: pointer; -} -.openerp .oe_import fieldset legend:before { - content: '\25BC '; -} -.openerp .oe_import fieldset.oe_closed legend:before { - content: '\25B6 '; -} -.openerp .oe_import fieldset.oe_closed table { - display: none; -} -.openerp .oe_import .separator.horizontal { - font-weight: bold; - border-bottom-width: 1px; - margin: 6px 4px 6px 1px; - height: 20px; -} -.openerp .duplicate_fld{ - background-color:#FF6666; -} -.openerp .select_fld{ - background: none repeat scroll 0 0 white; -} -.openerp .ui-autocomplete { - max-height: 300px; - overflow-y: auto; - padding-right: 20px; -} diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index dd014846884..8d75175e1e3 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -37,7 +37,10 @@ }; openerp.instances[new_instance._session_id] = new_instance; for(var i=0; i < modules.length; i++) { - openerp[modules[i]](new_instance,new_instance[modules[i]]); + new_instance[modules[i]] = {}; + if (openerp[modules[i]]) { + openerp[modules[i]](new_instance,new_instance[modules[i]]); + } } return new_instance; } diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 86fb457582d..01bfb63a580 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -13,7 +13,7 @@ instance.web.Notification = instance.web.Widget.extend({ }, start: function() { this._super.apply(this, arguments); - this.$element.notify({ + this.$el.notify({ speed: 500, expires: 2500 }); @@ -24,7 +24,7 @@ instance.web.Notification = instance.web.Widget.extend({ if (sticky) { opts.expires = false; } - this.$element.notify('create', { + this.$el.notify('create', { title: title, text: text }, opts); @@ -35,7 +35,7 @@ instance.web.Notification = instance.web.Widget.extend({ if (sticky) { opts.expires = false; } - this.$element.notify('create', 'oe_notification_alert', { + this.$el.notify('create', 'oe_notification_alert', { title: title, text: text }, opts); @@ -66,9 +66,9 @@ instance.web.Dialog = instance.web.Widget.extend({ max_width: '95%', height: 'auto', min_height: 0, - max_height: this.get_height('100%') - 140, + max_height: this.get_height('100%') - 200, autoOpen: false, - position: [false, 50], + position: [false, 40], buttons: {}, beforeClose: function () { self.on_close(); }, resizeStop: this.on_resized @@ -79,6 +79,9 @@ instance.web.Dialog = instance.web.Widget.extend({ } } if (options) { + if (options.buttons) { + this.params_buttons = true; + } _.extend(this.dialog_options, options); } }, @@ -109,7 +112,7 @@ instance.web.Dialog = instance.web.Widget.extend({ } else if (val.slice(-1) == "%") { return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10)); } else { - return parseInt(val, 10); + return parseInt(val, 10); } }, renderElement: function() { @@ -123,22 +126,32 @@ instance.web.Dialog = instance.web.Widget.extend({ if (! this.dialog_inited) this.init_dialog(); var o = this.get_options(options); - instance.web.dialog(this.$element, o).dialog('open'); + if (! this.params_buttons) { + this.$buttons.appendTo($("body")); + } + instance.web.dialog(this.$el, o).dialog('open'); + if (! this.params_buttons) { + this.$buttons.appendTo(this.$el.dialog("widget")); + } if (o.height === 'auto' && o.max_height) { - this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); + this.$el.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); } return this; }, init_dialog: function(options) { this.renderElement(); var o = this.get_options(options); - instance.web.dialog(this.$element, o); - var res = this.start(); + instance.web.dialog(this.$el, o); + if (! this.params_buttons) { + this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" />'); + this.$el.dialog("widget").append(this.$buttons); + } this.dialog_inited = true; + var res = this.start(); return res; }, close: function() { - this.$element.dialog('close'); + this.$el.dialog('close'); }, on_close: function() { if (this.__tmp_dialog_destroying) @@ -161,7 +174,7 @@ instance.web.Dialog = instance.web.Widget.extend({ this.__tmp_dialog_destroying = undefined; } if (! this.isDestroyed()) { - this.$element.dialog('destroy'); + this.$el.dialog('destroy'); } this._super(); } @@ -193,29 +206,9 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({ on_traceback: function(error) { var self = this; var buttons = {}; - if (instance.connection.openerp_entreprise) { - buttons[_t("Send OpenERP Enterprise Report")] = function() { - var $this = $(this); - var issuename = $('#issuename').val(); - var explanation = $('#explanation').val(); - var remark = $('#remark').val(); - // Call the send method from server to send mail with details - new instance.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){ - if (result === false) { - alert('There was a communication error.'); - } else { - $this.dialog('close'); - } - }); - }; - buttons[_t("Dont send")] = function() { - $(this).dialog("close"); - }; - } else { - buttons[_t("Ok")] = function() { - $(this).dialog("close"); - }; - } + buttons[_t("Ok")] = function() { + $(this).dialog("close"); + }; var dialog = new instance.web.Dialog(this, { title: "OpenERP " + _.str.capitalize(error.type), width: '80%', @@ -224,7 +217,7 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({ min_height: '600px', buttons: buttons }).open(); - dialog.$element.html(QWeb.render('CrashManager.error', {session: instance.connection, error: error})); + dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error})); }, on_javascript_exception: function(exception) { this.on_traceback({ @@ -241,22 +234,20 @@ instance.web.Loading = instance.web.Widget.extend({ this._super(parent); this.count = 0; this.blocked_ui = false; - var self = this; - this.request_call = function() { - self.on_rpc_event(1); - }; - this.response_call = function() { - self.on_rpc_event(-1); - }; - this.session.on_rpc_request.add_first(this.request_call); - this.session.on_rpc_response.add_last(this.response_call); + this.session.on("request", this, this.request_call); + this.session.on("response", this, this.response_call); + this.session.on("error", this, this.response_call); }, destroy: function() { - this.session.on_rpc_request.remove(this.request_call); - this.session.on_rpc_response.remove(this.response_call); this.on_rpc_event(-this.count); this._super(); }, + request_call: function() { + this.on_rpc_event(1); + }, + response_call: function() { + this.on_rpc_event(-1); + }, on_rpc_event : function(increment) { var self = this; if (!this.count && increment === 1) { @@ -269,13 +260,13 @@ instance.web.Loading = instance.web.Widget.extend({ this.count += increment; if (this.count > 0) { - if (instance.connection.debug) { - this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); + if (instance.session.debug) { + this.$el.text(_.str.sprintf( _t("Loading (%d)"), this.count)); } else { - this.$element.text(_t("Loading")); + this.$el.text(_t("Loading")); } - this.$element.show(); - this.getParent().$element.addClass('oe_wait'); + this.$el.show(); + this.getParent().$el.addClass('oe_wait'); } else { this.count = 0; clearTimeout(this.long_running_timer); @@ -284,8 +275,8 @@ instance.web.Loading = instance.web.Widget.extend({ this.blocked_ui = false; instance.web.unblockUI(); } - this.$element.fadeOut(); - this.getParent().$element.removeClass('oe_wait'); + this.$el.fadeOut(); + this.getParent().$el.removeClass('oe_wait'); } } }); @@ -300,6 +291,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ }, start: function() { var self = this; + $('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty(); var fetch_db = this.rpc("/web/database/get_list", {}).pipe( function(result) { self.db_list = result.db_list; @@ -315,17 +307,27 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ }, do_render: function() { var self = this; - self.$element.html(QWeb.render("DatabaseManager", { widget : self })); - self.$element.find(".oe_database_manager_menu").tabs({ - show: function(event, ui) { - $('*[autofocus]:first', ui.panel).focus(); - } + instance.webclient.toggle_bars(true); + self.$el.html(QWeb.render("DatabaseManager", { widget : self })); + $('.oe_user_menu_placeholder').append(QWeb.render("DatabaseManager.user_menu",{ widget : self })); + $('.oe_secondary_menus_container').append(QWeb.render("DatabaseManager.menu",{ widget : self })); + $('ul.oe_secondary_submenu > li:first').addClass('oe_active') + $('ul.oe_secondary_submenu > li').bind('click', function (event) { + var menuitem = $(this); + menuitem.addClass('oe_active').siblings().removeClass('oe_active'); + var form_id =menuitem.find('a').attr('href'); + $(form_id).show().siblings().hide(); + event.preventDefault(); }); - self.$element.find("form[name=create_db_form]").validate({ submitHandler: self.do_create }); - self.$element.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop }); - self.$element.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup }); - self.$element.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore }); - self.$element.find("form[name=change_pwd_form]").validate({ + $('#back-to-login').click(self.do_exit); + self.$el.find("td").addClass("oe_form_group_cell"); + self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label"); + self.$el.find("label").addClass("oe_form_label"); + self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create }); + self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop }); + self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup }); + self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore }); + self.$el.find("form[name=change_pwd_form]").validate({ messages: { old_pwd: "Please enter your previous password", new_pwd: "Please enter your new password", @@ -336,10 +338,9 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ }, submitHandler: self.do_change_password }); - self.$element.find("#back_to_login").click(self.do_exit); }, destroy: function () { - this.$element.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty(); + this.$el.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty(); this._super(); }, /** @@ -400,11 +401,13 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ 'db': form_obj['db_name'], 'login': 'admin', 'password': form_obj['create_admin_pwd'], + 'login_successful': function() { + self.do_action("reload"); + }, }, }; self.do_action(client_action); }); - }, do_drop: function(form) { var self = this; @@ -483,11 +486,14 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ self.display_error(result); return; } + self.unblockUI(); self.do_notify("Changed Password", "Password has been changed successfully"); }); }, do_exit: function () { - this.do_action("login"); + this.$el.remove(); + instance.webclient.toggle_bars(false); + this.do_action('login'); } }); instance.web.client_actions.add("database_manager", "instance.web.DatabaseManager"); @@ -495,11 +501,11 @@ instance.web.client_actions.add("database_manager", "instance.web.DatabaseManage instance.web.Login = instance.web.Widget.extend({ template: "Login", remember_credentials: true, - _db_list: null, init: function(parent, params) { this._super(parent); this.has_local_storage = typeof(localStorage) != 'undefined'; + this.db_list = null; this.selected_db = null; this.selected_login = null; this.params = params || {}; @@ -518,54 +524,47 @@ instance.web.Login = instance.web.Widget.extend({ }, start: function() { var self = this; - self.$element.find("form").submit(self.on_submit); - self.$element.find('.oe_login_manage_db').click(function() { + self.$el.find("form").submit(self.on_submit); + self.$el.find('.oe_login_manage_db').click(function() { self.do_action("database_manager"); }); - return self.load_db_list().then(self.on_db_list_loaded).then(function() { - if (self.params.db) { - self.do_login(self.params.db, self.params.login, self.params.password); + var d; + if (self.params.db) { + if (self.params.login && self.params.password) { + d = self.do_login(self.params.db, self.params.login, self.params.password); } - }); - }, - load_db_list: function (force) { - var d = $.when(), self = this; - if (_.isNull(this._db_list) || force) { - d = self.rpc("/web/database/get_list", {}, function(result) { - self._db_list = _.clone(result.db_list); - }, function(error, event) { - if (error.data.fault_code === 'AccessDenied') { - event.preventDefault(); - } - }); + } else { + d = self.rpc("/web/database/get_list", {}).done(self.on_db_loaded).fail(self.on_db_failed); } return d; }, - on_db_list_loaded: function () { - var self = this; - var list = this._db_list; - var dbdiv = this.$element.find('div.oe_login_dbpane'); - this.$element.find("[name=db]").replaceWith(instance.web.qweb.render('Login.dblist', { db_list: list, selected_db: this.selected_db})); - if(list.length === 0) { + on_db_loaded: function (result) { + this.db_list = result.db_list; + this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db})); + if(this.db_list.length === 0) { this.do_action("database_manager"); - } else if(list && list.length === 1) { - dbdiv.hide(); + } else if(this.db_list.length === 1) { + this.$('div.oe_login_dbpane').hide(); } else { - dbdiv.show(); + this.$('div.oe_login_dbpane').show(); + } + }, + on_db_failed: function (error, event) { + if (error.data.fault_code === 'AccessDenied') { + event.preventDefault(); } }, on_submit: function(ev) { if(ev) { ev.preventDefault(); } - var $e = this.$element; - var db = $e.find("form [name=db]").val(); + var db = this.$("form [name=db]").val(); if (!db) { this.do_warn("Login", "No database selected !"); return false; } - var login = $e.find("form input[name=login]").val(); - var password = $e.find("form input[name=password]").val(); + var login = this.$("form input[name=login]").val(); + var password = this.$("form input[name=password]").val(); this.do_login(db, login, password); }, @@ -578,7 +577,7 @@ instance.web.Login = instance.web.Widget.extend({ */ do_login: function (db, login, password) { var self = this; - this.$element.removeClass('oe_login_invalid'); + self.hide_error(); self.$(".oe_login_pane").fadeOut("slow"); return this.session.session_authenticate(db, login, password).pipe(function() { if (self.has_local_storage) { @@ -595,20 +594,95 @@ instance.web.Login = instance.web.Widget.extend({ } } self.trigger('login_successful'); - },function () { - self.$(".oe_login_pane").fadeIn("fast"); - self.$element.addClass("oe_login_invalid"); + }, function () { + self.$(".oe_login_pane").fadeIn("fast", function() { + self.show_error("Invalid username or password"); + }); }); }, - show: function () { - this.$element.show(); + show_error: function(message) { + this.$el.addClass("oe_login_invalid"); + this.$(".oe_login_error_message").text(message); + }, + hide_error: function() { + this.$el.removeClass('oe_login_invalid'); }, - hide: function () { - this.$element.hide(); - } }); instance.web.client_actions.add("login", "instance.web.Login"); +/** + * Client action to reload the whole interface. + * If params has an entry 'menu_id', it opens the given menu entry. + */ +instance.web.Reload = function(parent, params) { + var menu_id = (params && params.menu_id) || false; + var l = window.location; + + var sobj = $.deparam(l.search.substr(1)); + sobj.ts = new Date().getTime(); + var search = '?' + $.param(sobj); + + var hash = l.hash; + if (menu_id) { + hash = "#menu_id=" + menu_id; + } + var url = l.protocol + "//" + l.host + l.pathname + search + hash; + window.location = url; +}; +instance.web.client_actions.add("reload", "instance.web.Reload"); + +/** + * Client action to go back in breadcrumb history. + * If can't go back in history stack, will go back to home. + */ +instance.web.HistoryBack = function(parent, params) { + if (!parent.history_back()) { + window.location = '/' + (window.location.search || ''); + } +}; +instance.web.client_actions.add("history_back", "instance.web.HistoryBack"); + +/** + * Client action to go back home. + */ +instance.web.Home = instance.web.Widget.extend({ + init: function(parent, params) { + window.location = '/' + (window.location.search || ''); + } +}); +instance.web.client_actions.add("home", "instance.web.Home"); + +instance.web.ChangePassword = instance.web.Widget.extend({ + template: "ChangePassword", + start: function() { + var self = this; + self.$el.validate({ + submitHandler: function (form) { + self.rpc("/web/session/change_password",{ + 'fields': $(form).serializeArray() + }, function(result) { + if (result.error) { + self.display_error(result); + return; + } else { + instance.webclient.on_logout(); + } + }); + } + }); + }, + display_error: function (error) { + return instance.web.dialog($('<div>'), { + modal: true, + title: error.title, + buttons: [ + {text: _t("Ok"), click: function() { $(this).dialog("close"); }} + ] + }).html(error.error); + }, +}) +instance.web.client_actions.add("change_password", "instance.web.ChangePassword"); + instance.web.Menu = instance.web.Widget.extend({ template: 'Menu', init: function() { @@ -619,7 +693,7 @@ instance.web.Menu = instance.web.Widget.extend({ }, start: function() { this._super.apply(this, arguments); - this.$secondary_menus = this.getParent().$element.find('.oe_secondary_menus_container'); + this.$secondary_menus = this.getParent().$el.find('.oe_secondary_menus_container'); this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click); return this.do_reload(); }, @@ -631,8 +705,13 @@ instance.web.Menu = instance.web.Widget.extend({ this.data = data; this.renderElement(); this.limit_entries(); + // Hide toplevel item if there is only one + var $toplevel = this.$("li") + if($toplevel.length == 1) { + $toplevel.hide(); + } this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this })); - this.$element.on('click', 'a[data-menu]', this.on_menu_click); + this.$el.on('click', 'a[data-menu]', this.on_menu_click); // Hide second level submenus this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide(); if (self.current_menu) { @@ -647,8 +726,9 @@ instance.web.Menu = instance.web.Widget.extend({ } if (maximum_visible_links < this.data.data.children.length) { var $more = $(QWeb.render('Menu.more')), - $index = this.$element.find('li').eq(maximum_visible_links - 1); + $index = this.$el.find('li').eq(maximum_visible_links - 1); $index.after($more); + //$('.oe_topbar').append($more); $more.find('.oe_menu_more').append($index.next().nextAll()); } }, @@ -664,20 +744,22 @@ instance.web.Menu = instance.web.Widget.extend({ * @param {Number} id database id of the terminal menu to select */ open_menu: function (id) { + this.current_menu = id; + this.session.active_id = id; var $clicked_menu, $sub_menu, $main_menu; - $clicked_menu = this.$element.add(this.$secondary_menus).find('a[data-menu=' + id + ']'); + $clicked_menu = this.$el.add(this.$secondary_menus).find('a[data-menu=' + id + ']'); this.trigger('open_menu', id, $clicked_menu); if (this.$secondary_menus.has($clicked_menu).length) { $sub_menu = $clicked_menu.parents('.oe_secondary_menu'); - $main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']'); + $main_menu = this.$el.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']'); } else { $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']'); $main_menu = $clicked_menu; } // Activate current main menu - this.$element.find('.oe_active').removeClass('oe_active'); + this.$el.find('.oe_active').removeClass('oe_active'); $main_menu.addClass('oe_active'); // Show current sub menu @@ -704,7 +786,7 @@ instance.web.Menu = instance.web.Widget.extend({ * @param {Number} id the action_id to match */ open_action: function (id) { - var $menu = this.$element.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]'); + var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]'); var menu_id = $menu.data('menu'); if (menu_id) { this.open_menu(menu_id); @@ -720,14 +802,14 @@ instance.web.Menu = instance.web.Widget.extend({ if (!id) { return; } // find back the menuitem in dom to get the action - var $item = this.$element.find('a[data-menu=' + id + ']'); + var $item = this.$el.find('a[data-menu=' + id + ']'); if (!$item.length) { $item = this.$secondary_menus.find('a[data-menu=' + id + ']'); } var action_id = $item.data('action-id'); // If first level menu doesnt have action trigger first leaf if (!action_id) { - if(this.$element.has($item).length) { + if(this.$el.has($item).length) { var $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']'); var $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]'); if($items.length) { @@ -736,16 +818,15 @@ instance.web.Menu = instance.web.Widget.extend({ } } } - this.open_menu(id); - this.current_menu = id; - this.session.active_id = id; if (action_id) { this.trigger('menu_click', { action_id: action_id, needaction: needaction, - id: id + id: id, + previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu) }, $item); } + this.open_menu(id); }, /** * Jquery event handler for menu click @@ -768,7 +849,7 @@ instance.web.UserMenu = instance.web.Widget.extend({ start: function() { var self = this; this._super.apply(this, arguments); - this.$element.on('click', '.oe_dropdown_menu li a[data-menu]', function(ev) { + this.$el.on('click', '.oe_dropdown_menu li a[data-menu]', function(ev) { ev.preventDefault(); var f = self['on_menu_' + $(this).data('menu')]; if (f) { @@ -776,53 +857,22 @@ instance.web.UserMenu = instance.web.Widget.extend({ } }); }, - change_password :function() { - var self = this; - this.dialog = new instance.web.Dialog(this, { - title: _t("Change Password"), - width : 'auto' - }).open(); - this.dialog.$element.html(QWeb.render("UserMenu.password", self)); - this.dialog.$element.find("form[name=change_password_form]").validate({ - submitHandler: function (form) { - self.rpc("/web/session/change_password",{ - 'fields': $(form).serializeArray() - }, function(result) { - if (result.error) { - self.display_error(result); - return; - } else { - instance.webclient.on_logout(); - } - }); - } - }); - }, - display_error: function (error) { - return instance.web.dialog($('<div>'), { - modal: true, - title: error.title, - buttons: [ - {text: _("Ok"), click: function() { $(this).dialog("close"); }} - ] - }).html(error.error); - }, do_update: function () { var self = this; var fct = function() { - var $avatar = self.$element.find('.oe_topbar_avatar'); + var $avatar = self.$el.find('.oe_topbar_avatar'); $avatar.attr('src', $avatar.data('default-src')); if (!self.session.uid) return; var func = new instance.web.Model("res.users").get_func("read"); return func(self.session.uid, ["name", "company_id"]).pipe(function(res) { var topbar_name = res.name; - if(instance.connection.debug) - topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.connection.db); + if(instance.session.debug) + topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.session.db); if(res.company_id[0] > 1) topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]); - self.$element.find('.oe_topbar_name').text(topbar_name); - var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=avatar&id=%s', self.session.prefix, self.session.session_id, self.session.uid); + self.$el.find('.oe_topbar_name').text(topbar_name); + var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=image_small&id=%s', self.session.prefix, self.session.session_id, self.session.uid); $avatar.attr('src', avatar_src); }); }; @@ -834,44 +884,12 @@ instance.web.UserMenu = instance.web.Widget.extend({ }, on_menu_settings: function() { var self = this; - var action_manager = new instance.web.ActionManager(this); - var dataset = new instance.web.DataSet (this,'res.users',this.context); - dataset.call ('action_get','',function (result){ - self.rpc('/web/action/load', {action_id:result}, function(result){ - action_manager.do_action(_.extend(result['result'], { - target: 'inline', - res_id: self.session.uid, - res_model: 'res.users', - flags: { - action_buttons: false, - search_view: false, - sidebar: false, - views_switcher: false, - pager: false - } - })); + if (!this.getParent().has_uncommitted_changes()) { + self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) { + result.result.res_id = instance.session.uid; + self.getParent().action_manager.do_action(result.result); }); - }); - this.dialog = new instance.web.Dialog(this,{ - title: _t("Preferences"), - width: '700px', - buttons: [ - {text: _t("Change password"), click: function(){ self.change_password(); }}, - {text: _t("Cancel"), click: function(){ $(this).dialog('destroy'); }}, - {text: _t("Save"), click: function(){ - var inner_widget = action_manager.inner_widget; - inner_widget.views[inner_widget.active_view].controller.do_save() - .then(function() { - self.dialog.destroy(); - // needs to refresh interface in case language changed - window.location.reload(); - }); - } - } - ] - }).open(); - action_manager.appendTo(this.dialog.$element); - action_manager.renderElement(this.dialog); + } }, on_menu_about: function() { var self = this; @@ -883,7 +901,7 @@ instance.web.UserMenu = instance.web.Widget.extend({ window.location.href, 'debug'); }); instance.web.dialog($help, {autoOpen: true, - modal: true, width: 580, height: 290, resizable: false, title: _t("About")}); + modal: true, width: 507, height: 290, resizable: false, title: _t("About")}); }); }, }); @@ -896,7 +914,7 @@ instance.web.Client = instance.web.Widget.extend({ }, start: function() { var self = this; - return instance.connection.session_bind(this.origin).pipe(function() { + return instance.session.session_bind(this.origin).pipe(function() { var $e = $(QWeb.render(self._template, {})); self.replaceElement($e); self.bind_events(); @@ -905,10 +923,10 @@ instance.web.Client = instance.web.Widget.extend({ }, bind_events: function() { var self = this; - this.$element.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() { + this.$el.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() { $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter'); }); - this.$element.on('click', '.oe_dropdown_toggle', function(ev) { + this.$el.on('click', '.oe_dropdown_toggle', function(ev) { ev.preventDefault(); var $toggle = $(this); var $menu = $toggle.siblings('.oe_dropdown_menu'); @@ -930,22 +948,29 @@ instance.web.Client = instance.web.Widget.extend({ }, 0); }); instance.web.bus.on('click', this, function(ev) { + $.fn.tipsy.clear(); if (!$(ev.target).is('input[type=file]')) { - self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened'); + self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened'); } }); }, show_common: function() { var self = this; this.crashmanager = new instance.web.CrashManager(); - instance.connection.on_rpc_error.add(this.crashmanager.on_rpc_error); + instance.session.on_rpc_error.add(this.crashmanager.on_rpc_error); self.notification = new instance.web.Notification(this); - self.notification.appendTo(self.$element); + self.notification.appendTo(self.$el); self.loading = new instance.web.Loading(self); - self.loading.appendTo(self.$element); + self.loading.appendTo(self.$el); self.action_manager = new instance.web.ActionManager(self); self.action_manager.appendTo(self.$('.oe_application')); }, + toggle_bars: function(value) { + this.$('tr:has(td.oe_topbar),.oe_leftbar').toggle(value); + }, + has_uncommitted_changes: function() { + return false; + }, }); instance.web.WebClient = instance.web.Client.extend({ @@ -957,6 +982,9 @@ instance.web.WebClient = instance.web.Client.extend({ start: function() { var self = this; return $.when(this._super()).pipe(function() { + self.$el.on('click', '.oe_logo', function() { + self.action_manager.do_action('home'); + }); if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) { $("body").addClass("kitten-mode-activated"); if ($.blockUI) { @@ -987,38 +1015,48 @@ instance.web.WebClient = instance.web.Client.extend({ }; }, show_login: function() { - this.$('.oe_topbar').hide(); - this.action_manager.do_action("login"); - this.action_manager.inner_widget.on('login_successful', this, this.show_application); + this.toggle_bars(false); + + var action = { + 'type': 'ir.actions.client', + 'tag': 'login' + }; + var state = $.bbq.getState(true); + if (state.action === "login") { + action.params = state; + } + + this.action_manager.do_action(action); + this.action_manager.inner_widget.on('login_successful', this, function() { + this.do_push_state(state); + this._current_state = null; // ensure the state will be loaded + this.show_application(); // will load the state we just pushed + }); }, show_application: function() { var self = this; - self.$('.oe_topbar').show(); + self.toggle_bars(true); self.menu = new instance.web.Menu(self); - self.menu.replace(this.$element.find('.oe_menu_placeholder')); + self.menu.replace(this.$el.find('.oe_menu_placeholder')); self.menu.on('menu_click', this, this.on_menu_action); self.user_menu = new instance.web.UserMenu(self); - self.user_menu.replace(this.$element.find('.oe_user_menu_placeholder')); + self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder')); self.user_menu.on_menu_logout.add(this.proxy('on_logout')); self.user_menu.on_action.add(this.proxy('on_menu_action')); self.user_menu.do_update(); self.bind_hashchange(); - if (!self.session.openerp_entreprise) { - var version_label = _t("OpenERP - Unsupported/Community Version"); - self.$element.find('.oe_footer_powered').append(_.str.sprintf('<span> - <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">%s</a></span>', version_label)); - } self.set_title(); }, destroy_content: function() { _.each(_.clone(this.getChildren()), function(el) { el.destroy(); }); - this.$element.children().remove(); + this.$el.children().remove(); }, do_reload: function() { var self = this; return this.session.session_reload().pipe(function () { - instance.connection.load_modules(true).pipe( + instance.session.load_modules(true).pipe( self.menu.proxy('do_reload')); }); }, @@ -1032,28 +1070,28 @@ instance.web.WebClient = instance.web.Client.extend({ }, on_logout: function() { var self = this; - this.session.session_logout().then(function () { - $(window).unbind('hashchange', self.on_hashchange); - self.do_push_state({}); - //would be cool to be able to do this, but I think it will make addons do strange things - //this.show_login(); - window.location.reload(); - }); + if (!this.has_uncommitted_changes()) { + this.session.session_logout().then(function () { + $(window).unbind('hashchange', self.on_hashchange); + self.do_push_state({}); + window.location.reload(); + }); + } }, bind_hashchange: function() { var self = this; $(window).bind('hashchange', this.on_hashchange); var state = $.bbq.getState(true); - if (! _.isEmpty(state)) { - $(window).trigger('hashchange'); - } else { + if (_.isEmpty(state) || state.action == "login") { self.menu.has_been_loaded.then(function() { - var first_menu_id = self.menu.$element.find("a:first").data("menu"); + var first_menu_id = self.menu.$el.find("a:first").data("menu"); if(first_menu_id) { self.menu.menu_click(first_menu_id); } }); + } else { + $(window).trigger('hashchange'); } }, on_hashchange: function(event) { @@ -1081,34 +1119,35 @@ instance.web.WebClient = instance.web.Client.extend({ }, on_menu_action: function(options) { var self = this; - this.rpc("/web/action/load", { action_id: options.action_id }) - .then(function (result) { + return this.rpc("/web/action/load", { action_id: options.action_id }) + .pipe(function (result) { var action = result.result; if (options.needaction) { action.context.search_default_needaction_pending = true; } - self.action_manager.clear_breadcrumbs(); - self.action_manager.do_action(action); - }); - }, - do_action: function(action) { - var self = this; - // TODO replace by client action menuclick - if(action.menu_id) { - this.do_reload().then(function () { - self.menu.menu_click(action.menu_id); + return $.when(self.action_manager.do_action(action, null, true)).fail(function() { + self.menu.open_menu(options.previous_menu_id); + }); }); - } }, set_content_full_screen: function(fullscreen) { if (fullscreen) { - $(".oe_webclient", this.$element).addClass("oe_content_full_screen"); + $(".oe_webclient", this.$el).addClass("oe_content_full_screen"); $("body").css({'overflow-y':'hidden'}); } else { - $(".oe_webclient", this.$element).removeClass("oe_content_full_screen"); + $(".oe_webclient", this.$el).removeClass("oe_content_full_screen"); $("body").css({'overflow-y':'scroll'}); } - } + }, + has_uncommitted_changes: function() { + var $e = $.Event('clear_uncommitted_changes'); + instance.web.bus.trigger('clear_uncommitted_changes', $e); + if ($e.isDefaultPrevented()) { + return true; + } else { + return this._super.apply(this, arguments); + } + }, }); instance.web.EmbeddedClient = instance.web.Client.extend({ @@ -1125,7 +1164,7 @@ instance.web.EmbeddedClient = instance.web.Client.extend({ start: function() { var self = this; return $.when(this._super()).pipe(function() { - return instance.connection.session_authenticate(self.dbname, self.login, self.key, true).pipe(function() { + return instance.session.session_authenticate(self.dbname, self.login, self.key, true).pipe(function() { return self.rpc("/web/action/load", { action_id: self.action_id }, function(result) { var action = result.result; action.flags = _.extend({ diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index bc506161db5..228cee5e411 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -525,8 +525,8 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { _.each(this.getChildren(), function(el) { el.destroy(); }); - if(this.$element) { - this.$element.remove(); + if(this.$el) { + this.$el.remove(); } instance.web.PropertiesMixin.destroy.call(this); }, @@ -538,7 +538,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { appendTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.appendTo(t); + self.$el.appendTo(t); }, target); }, /** @@ -549,7 +549,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { prependTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.prependTo(t); + self.$el.prependTo(t); }, target); }, /** @@ -560,7 +560,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertAfter: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.insertAfter(t); + self.$el.insertAfter(t); }, target); }, /** @@ -571,7 +571,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertBefore: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.insertBefore(t); + self.$el.insertBefore(t); }, target); }, /** @@ -581,7 +581,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { */ replace: function(target) { return this.__widgetRenderAndInsert(_.bind(function(t) { - this.$element.replaceAll(t); + this.$el.replaceAll(t); }, this), target); }, __widgetRenderAndInsert: function(insertion, target) { @@ -640,8 +640,8 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn * // stuff that you want to init before the rendering * }, * start: function() { - * // stuff you want to make after the rendering, `this.$element` holds a correct value - * this.$element.find(".my_button").click(/* an example of event binding * /); + * // stuff you want to make after the rendering, `this.$el` holds a correct value + * this.$el.find(".my_button").click(/* an example of event binding * /); * * // if you have some asynchronous operations, it's a good idea to return * // a promise in start() @@ -692,7 +692,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { instance.web.WidgetMixin.init.call(this,parent); // FIXME: this should not be this.setElement(this._make_descriptive()); - this.session = instance.connection; + this.session = instance.session; }, /** * Renders the element. The default implementation renders the widget using QWeb, @@ -718,15 +718,15 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @returns {*} this */ replaceElement: function ($el) { - var $oldel = this.$element; + var $oldel = this.$el; this.setElement($el); - if ($oldel && !$oldel.is(this.$element)) { - $oldel.replaceWith(this.$element); + if ($oldel && !$oldel.is(this.$el)) { + $oldel.replaceWith(this.$el); } return this; }, /** - * Re-sets the widget's root element (el/$el/$element). + * Re-sets the widget's root element (el/$el/$el). * * Includes: * * re-delegating events @@ -738,14 +738,14 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @return {*} this */ setElement: function (element) { - // NB: completely useless, as WidgetMixin#init creates a $element + // NB: completely useless, as WidgetMixin#init creates a $el // always - if (this.$element) { + if (this.$el) { this.undelegateEvents(); } - this.$element = (element instanceof $) ? element : $(element); - this.el = this.$element[0]; + this.$el = (element instanceof $) ? element : $(element); + this.el = this.$el[0]; this.delegateEvents(); @@ -797,32 +797,33 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { event += '.widget_events'; if (!selector) { - this.$element.on(event, method); + this.$el.on(event, method); } else { - this.$element.on(event, selector, method); + this.$el.on(event, selector, method); } } }, undelegateEvents: function () { - this.$element.off('.widget_events'); + this.$el.off('.widget_events'); }, /** - * Shortcut for ``this.$element.find(selector)`` + * Shortcut for ``this.$el.find(selector)`` * * @param {String} selector CSS selector, rooted in $el * @returns {jQuery} selector match */ $: function(selector) { - return this.$element.find(selector); + return this.$el.find(selector); }, /** * Informs the action manager to do an action. This supposes that * the action manager can be found amongst the ancestors of the current widget. * If that's not the case this method will simply return `false`. */ - do_action: function(action, on_finished) { - if (this.getParent()) { - return this.getParent().do_action(action, on_finished); + do_action: function() { + var parent = this.getParent(); + if (parent) { + return parent.do_action.apply(parent, arguments); } return false; }, @@ -841,7 +842,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { rpc: function(url, data, success, error) { var def = $.Deferred().then(success, error); var self = this; - instance.connection.rpc(url, data). then(function() { + instance.session.rpc(url, data). then(function() { if (!self.isDestroyed()) def.resolve.apply(def, arguments); }, function() { @@ -863,7 +864,7 @@ instance.web.Registry = instance.web.Class.extend({ * * An object path is simply a dotted name from the instance root to the * object pointed to (e.g. ``"instance.web.Session"`` for an OpenERP - * connection object). + * session object). * * @constructs instance.web.Registry * @param {Object} mapping a mapping of keys to object-paths @@ -972,6 +973,11 @@ instance.web.Registry = instance.web.Class.extend({ }); instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ + triggers: { + 'request': 'Request sent', + 'response': 'Response received', + 'error': 'HTTP Error response or timeout received', + }, /** * @constructs instance.web.JsonRPC * @extends instance.web.CallbackEnabled @@ -1089,12 +1095,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ id: _.uniqueId('r') }; var deferred = $.Deferred(); - this.on_rpc_request(); + this.trigger('request', url, payload); var aborter = params.aborter; delete params.aborter; var request = this.rpc_function(url, payload).then( function (response, textStatus, jqXHR) { - self.on_rpc_response(); + self.trigger('response', response); if (!response.error) { if (url.url === '/web/session/eval_domain_and_context') { self.test_eval(params, response.result); @@ -1102,18 +1108,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ deferred.resolve(response["result"], textStatus, jqXHR); } else if (response.error.data.type === "session_invalid") { self.uid = false; - // TODO deprecate or use a deferred on login.do_ask_login() - self.on_session_invalid(function() { - self.rpc(url, payload.params, - function() { deferred.resolve.apply(deferred, arguments); }, - function() { deferred.reject.apply(deferred, arguments); }); - }); } else { deferred.reject(response.error, $.Event()); } }, function(jqXHR, textStatus, errorThrown) { - self.on_rpc_response(); + self.trigger('error'); var error = { code: -32098, message: "XmlHttpRequestError " + errorThrown, @@ -1221,10 +1221,6 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ return deferred; } }, - on_rpc_request: function() { - }, - on_rpc_response: function() { - }, on_rpc_error: function(error) { }, get_url: function (file) { diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 2f2fb4f7832..d55408ebc83 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -11,32 +11,6 @@ if (!console.debug) { openerp.web.coresetup = function(instance) { -/** - * @deprecated use :class:`instance.web.Widget` - */ -instance.web.OldWidget = instance.web.Widget.extend({ - init: function(parent, element_id) { - this._super(parent); - this.element_id = element_id; - this.element_id = this.element_id || _.uniqueId('widget-'); - - var tmp = document.getElementById(this.element_id); - this.setElement(tmp || this._make_descriptive()); - }, - renderElement: function() { - var rendered = this.render(); - if (rendered) { - this.replaceElement($(rendered)); - } - return this; - }, - render: function (additional) { - if (this.template) - return instance.web.qweb.render(this.template, _.extend({widget: this}, additional || {})); - return null; - } -}); - /** Session openerp specific RPC class */ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Session# */{ init: function() { @@ -61,7 +35,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess this.username = false; this.user_context= {}; this.db = false; - this.openerp_entreprise = false; this.module_list = instance._modules.slice(); this.module_loaded = {}; _(this.module_list).each(function (mod) { @@ -84,7 +57,12 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess if(self.session_is_valid()) { return deferred.pipe(function() { return self.load_modules(); }); } - return deferred; + return $.when( + deferred, + self.rpc('/web/webclient/bootstrap_translations', {mods: instance._modules}).pipe(function(trans) { + instance.web._t.database.set_bundle(trans); + }) + ); }); }, /** @@ -104,8 +82,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess db: result.db, username: result.login, uid: result.uid, - user_context: result.context, - openerp_entreprise: result.openerp_entreprise + user_context: result.context }); }); }, @@ -129,8 +106,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess db: result.db, username: result.login, uid: result.uid, - user_context: result.context, - openerp_entreprise: result.openerp_entreprise + user_context: result.context }); if (!_volatile) { self.set_cookie('session_id', self.session_id); @@ -142,20 +118,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess this.set_cookie('session_id', ''); return this.rpc("/web/session/destroy", {}); }, - on_session_valid: function() { - }, - /** - * Called when a rpc call fail due to an invalid session. - * By default, it's a noop - */ - on_session_invalid: function(retry_callback) { - }, - /** - * Fetches a cookie stored by an openerp session - * - * @private - * @param name the cookie's name - */ get_cookie: function (name) { if (!this.name) { return null; } var nameEQ = this.name + '|' + name + '='; @@ -189,9 +151,8 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess /** * Load additional web addons of that instance and init them * - * @param {Boolean} [no_session_valid_signal=false] prevents load_module from triggering ``on_session_valid``. */ - load_modules: function(no_session_valid_signal) { + load_modules: function() { var self = this; return this.rpc('/web/session/modules', {}).pipe(function(result) { var lang = self.user_context.lang, @@ -200,32 +161,31 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess var to_load = _.difference(result, self.module_list).join(','); self.module_list = all_modules; - var loaded = $.Deferred().resolve().promise(); - if (to_load.length) { + var loaded = self.rpc('/web/webclient/translations', params).then(function(trans) { + instance.web._t.database.set_bundle(trans); + }); + var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; + if(to_load.length) { loaded = $.when( - self.rpc('/web/webclient/csslist', {mods: to_load}, self.do_load_css), + loaded, + self.rpc('/web/webclient/csslist', {mods: to_load}).then(self.do_load_css), self.rpc('/web/webclient/qweblist', {mods: to_load}).pipe(self.do_load_qweb), - self.rpc('/web/webclient/translations', params).pipe(function(trans) { - instance.web._t.database.set_bundle(trans); - var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; - return self.rpc('/web/webclient/jslist', {mods: to_load}).pipe(function(files) { - return self.do_load_js(file_list.concat(files)); - }).then(function () { - if (!Date.CultureInfo.pmDesignator) { - // If no am/pm designator is specified but the openerp - // datetime format uses %i, date.js won't be able to - // correctly format a date. See bug#938497. - Date.CultureInfo.amDesignator = 'AM'; - Date.CultureInfo.pmDesignator = 'PM'; - } - }); - })) + self.rpc('/web/webclient/jslist', {mods: to_load}).then(function(files) { + file_list = file_list.concat(files); + }) + ); } - return loaded.then(function() { + return loaded.pipe(function () { + return self.do_load_js(file_list); + }).then(function() { self.on_modules_loaded(); self.trigger('module_loaded'); - if (!no_session_valid_signal) { - self.on_session_valid(); + if (!Date.CultureInfo.pmDesignator) { + // If no am/pm designator is specified but the openerp + // datetime format uses %i, date.js won't be able to + // correctly format a date. See bug#938497. + Date.CultureInfo.amDesignator = 'AM'; + Date.CultureInfo.pmDesignator = 'PM'; } }); }); @@ -540,52 +500,14 @@ $.async_when = function() { // special tweak for the web client var old_async_when = $.async_when; $.async_when = function() { - if (instance.connection.synch) + if (instance.session.synch) return $.when.apply(this, arguments); else return old_async_when.apply(this, arguments); }; -/** Setup blockui */ -if ($.blockUI) { - $.blockUI.defaults.baseZ = 1100; - $.blockUI.defaults.message = '<div class="oe_blockui_spin" style="height: 50px">'; - $.blockUI.defaults.css.border = '0'; - $.blockUI.defaults.css["background-color"] = ''; - $.blockUI.spinners = []; -} -instance.web.blockUI = function() { - var tmp = $.blockUI.apply($, arguments); - var target = $(".oe_blockui_spin")[0]; - var opts = { - lines: 13, // The number of lines to draw - length: 7, // The length of each line - width: 4, // The line thickness - radius: 10, // The radius of the inner circle - rotate: 0, // The rotation offset - color: '#FFF', // #rgb or #rrggbb - speed: 1, // Rounds per second - trail: 60, // Afterglow percentage - shadow: false, // Whether to render a shadow - hwaccel: false, // Whether to use hardware acceleration - className: 'spinner', // The CSS class to assign to the spinner - zIndex: 2e9, // The z-index (defaults to 2000000000) - top: 'auto', // Top position relative to parent in px - left: 'auto' // Left position relative to parent in px - }; - var spinner = new Spinner(opts).spin(target); - $.blockUI.spinners.push(spinner); - return tmp; -} -instance.web.unblockUI = function() { - _.each($.blockUI.spinners, function(el) { - el.stop(); - }); - return $.unblockUI.apply($, arguments); -} - /** Setup default session */ -instance.connection = new instance.web.Session(); +instance.session = new instance.web.Session(); /** Configure default qweb */ instance.web._t = new instance.web.TranslationDataBase().build_translation_function(); @@ -604,8 +526,8 @@ instance.web._lt = function (s) { return {toString: function () { return instance.web._t(s); }} }; instance.web.qweb = new QWeb2.Engine(); -instance.web.qweb.default_dict['__debug__'] = instance.connection.debug; // Which one ? -instance.web.qweb.debug = instance.connection.debug; +instance.web.qweb.default_dict['__debug__'] = instance.session.debug; // Which one ? +instance.web.qweb.debug = instance.session.debug; instance.web.qweb.default_dict = { '_' : _, '_t' : instance.web._t @@ -620,13 +542,9 @@ instance.web.qweb.preprocess_node = function() { if (translation && translation.value === 'off') { return; } - var ts = _.str.trim(this.node.data); - if (ts.length === 0) { - return; - } - var tr = instance.web._t(ts); - if (tr !== ts) { - this.node.data = tr; + var match = /^(\s*)(.+?)(\s*)$/.exec(this.node.data); + if (match) { + this.node.data = match[1] + instance.web._t(match[2]) + match[3]; } break; case 1: @@ -662,42 +580,96 @@ var _t = instance.web._t; _t('%d years ago'); } -instance.connection.on('module_loaded', this, function () { +instance.session.on('module_loaded', this, function () { // provide timeago.js with our own translator method $.timeago.settings.translator = instance.web._t; }); +/** Setup blockui */ +if ($.blockUI) { + $.blockUI.defaults.baseZ = 1100; + $.blockUI.defaults.message = '<div class="openerp oe_blockui_spin_container" style="background-color: transparent;">'; + $.blockUI.defaults.css.border = '0'; + $.blockUI.defaults.css["background-color"] = ''; +} + +var messages_by_seconds = function() { + return [ + [0, _t("Loading...")], + [20, _t("Still loading...")], + [60, _t("Still loading...<br />Please be patient.")], + [120, _t("Don't leave yet,<br />it's still loading...")], + [300, _t("You may not believe it,<br />but the application is actually loading...")], + [420, _t("Take a minute to get a coffee,<br />because it's loading...")], + [3600, _t("Maybe you should consider reloading the application by pressing F5...")], + ]; +}; + +instance.web.Throbber = instance.web.Widget.extend({ + template: "Throbber", + start: function() { + var opts = { + lines: 13, // The number of lines to draw + length: 7, // The length of each line + width: 4, // The line thickness + radius: 10, // The radius of the inner circle + rotate: 0, // The rotation offset + color: '#FFF', // #rgb or #rrggbb + speed: 1, // Rounds per second + trail: 60, // Afterglow percentage + shadow: false, // Whether to render a shadow + hwaccel: false, // Whether to use hardware acceleration + className: 'spinner', // The CSS class to assign to the spinner + zIndex: 2e9, // The z-index (defaults to 2000000000) + top: 'auto', // Top position relative to parent in px + left: 'auto' // Left position relative to parent in px + }; + this.spin = new Spinner(opts).spin(this.$el[0]); + this.start_time = new Date().getTime(); + this.act_message(); + }, + act_message: function() { + var self = this; + setTimeout(function() { + if (self.isDestroyed()) + return; + var seconds = (new Date().getTime() - self.start_time) / 1000; + var mes; + _.each(messages_by_seconds(), function(el) { + if (seconds >= el[0]) + mes = el[1]; + }); + self.$(".oe_throbber_message").html(mes); + self.act_message(); + }, 1000); + }, + destroy: function() { + if (this.spin) + this.spin.stop(); + this._super(); + }, +}); +instance.web.Throbber.throbbers = []; + +instance.web.blockUI = function() { + var tmp = $.blockUI.apply($, arguments); + var throbber = new instance.web.Throbber(); + instance.web.Throbber.throbbers.push(throbber); + throbber.appendTo($(".oe_blockui_spin_container")); + return tmp; +} +instance.web.unblockUI = function() { + _.each(instance.web.Throbber.throbbers, function(el) { + el.destroy(); + }); + return $.unblockUI.apply($, arguments); +} + /** * Registry for all the client actions key: tag value: widget */ instance.web.client_actions = new instance.web.Registry(); -/** - * Client action to reload the whole interface. - * If params has an entry 'menu_id', it opens the given menu entry. - */ -instance.web.Reload = instance.web.Widget.extend({ - init: function(parent, params) { - this._super(parent); - this.menu_id = (params && params.menu_id) || false; - }, - start: function() { - var l = window.location; - var timestamp = new Date().getTime(); - var search = "?ts=" + timestamp; - if (l.search) { - search = l.search + "&ts=" + timestamp; - } - var hash = l.hash; - if (this.menu_id) { - hash = "#menu_id=" + this.menu_id; - } - var url = l.protocol + "//" + l.host + l.pathname + search + hash; - window.location = url; - } -}); -instance.web.client_actions.add("reload", "instance.web.Reload"); - }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 2589afe5503..2b82e618203 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -58,7 +58,7 @@ instance.web.Query = instance.web.Class.extend({ }, _execute: function () { var self = this; - return instance.connection.rpc('/web/dataset/search_read', { + return instance.session.rpc('/web/dataset/search_read', { model: this._model.name, fields: this._fields || false, domain: this._model.domain(this._filter), @@ -107,7 +107,7 @@ instance.web.Query = instance.web.Class.extend({ * Performs a groups read according to the provided grouping criterion * * @param {String|Array<String>} grouping - * @returns {jQuery.Deferred<Array<openerp.web.data.Group>> | null} + * @returns {jQuery.Deferred<Array<openerp.web.QueryGroup>> | null} */ group_by: function (grouping) { if (grouping === undefined) { @@ -130,7 +130,7 @@ instance.web.Query = instance.web.Class.extend({ orderby: instance.web.serialize_sort(this._order_by) || false }).pipe(function (results) { return _(results).map(function (result) { - return new instance.web.data.Group( + return new instance.web.QueryGroup( self._model.name, grouping[0], result); }); }); @@ -194,7 +194,54 @@ instance.web.Query = instance.web.Class.extend({ } }); -instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ +instance.web.QueryGroup = instance.web.Class.extend({ + init: function (model, grouping_field, 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 + var fixed_group = _.extend( + {__context: {group_by: []}, __domain: []}, + read_group_group); + + var aggregates = {}; + _(fixed_group).each(function (value, key) { + if (key.indexOf('__') === 0 + || key === grouping_field + || key === grouping_field + '_count') { + return; + } + aggregates[key] = value || 0; + }); + + this.model = new instance.web.Model( + model, fixed_group.__context, fixed_group.__domain); + + var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0; + var leaf_group = fixed_group.__context.group_by.length === 0; + this.attributes = { + folded: !!(fixed_group.__fold), + grouped_on: grouping_field, + // if terminal group (or no group) and group_by_no_leaf => use group.__count + length: group_size, + value: fixed_group[grouping_field], + // 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']), + + aggregates: aggregates + }; + }, + get: function (key) { + return this.attributes[key]; + }, + subgroups: function () { + return this.model.query().group_by(this.model.context().group_by); + }, + query: function () { + return this.model.query.apply(this.model, arguments); + } +}); + +instance.web.Model = instance.web.Class.extend({ /** * @constructs instance.web.Model * @extends instance.web.Class @@ -233,7 +280,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ kwargs = args; args = []; } - return instance.connection.rpc('/web/dataset/call_kw', { + return instance.session.rpc('/web/dataset/call_kw', { model: this.name, method: method, args: args, @@ -256,7 +303,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ * @param {String} signal signal to trigger on the workflow */ exec_workflow: function (id, signal) { - return instance.connection.rpc('/web/dataset/exec_workflow', { + return instance.session.rpc('/web/dataset/exec_workflow', { model: this.name, id: id, signal: signal @@ -282,7 +329,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ */ context: function (context) { return new instance.web.CompoundContext( - instance.connection.user_context, this._context, context || {}); + instance.session.user_context, this._context, context || {}); }, /** * Button action caller, needs to perform cleanup if an action is returned @@ -292,7 +339,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ * FIXME: remove when evaluator integrated */ call_button: function (method, args) { - return instance.connection.rpc('/web/dataset/call_button', { + return instance.session.rpc('/web/dataset/call_button', { model: this.name, method: method, domain_id: null, @@ -302,144 +349,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ }, }); -instance.web.Traverser = instance.web.Class.extend(/** @lends openerp.web.Traverser# */{ - /** - * @constructs instance.web.Traverser - * @extends instance.web.Class - * - * @param {instance.web.Model} model instance this traverser is bound to - */ - init: function (model) { - this._model = model; - this._index = 0; - }, - - /** - * Gets and sets the current index - * - * @param {Number} [idx] - * @returns {Number} current index - */ - index: function (idx) { - if (idx) { this._index = idx; } - return this._index; - }, - /** - * Returns the model this traverser is currently bound to - * - * @returns {openerp.web.Model} - */ - model: function () { - return this._model; - }, - /** - * Fetches the size of the backing model's match - * - * @returns {Deferred<Number>} deferred count - */ - size: function () { - return this._model.query().count(); - }, - - /** - * Record at the current index for the collection, fails if there is no - * record at the current index. - * - * @returns {Deferred<>} - */ - current: function (fields) { - return this._model.query(fields).first().pipe(function (record) { - if (record == null) { - return $.Deferred() - .reject('No record at index' + this._index) - .promise(); - } - return record; - }); - }, - next: function (fields) { - var self = this; - this._index++; - return this.size().pipe(function (s) { - if (self._index >= s) { - self._index = 0; - } - return self.current(fields); - }); - }, - previous: function (fields) { - var self = this; - this._index--; - if (this._index < 0) { - return this.size().pipe(function (s) { - self._index = s-1; - return self.current(fields); - }); - } - return this.current(fields); - } - -}); - -/** - * Utility objects, should never need to be instantiated from outside of this - * module - * - * @namespace - */ -instance.web.data = { - Group: instance.web.Class.extend(/** @lends openerp.web.data.Group# */{ - /** - * @constructs instance.web.data.Group - * @extends instance.web.Class - */ - init: function (model, grouping_field, 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 - var fixed_group = _.extend( - {__context: {group_by: []}, __domain: []}, - read_group_group); - - var aggregates = {}; - _(fixed_group).each(function (value, key) { - if (key.indexOf('__') === 0 - || key === grouping_field - || key === grouping_field + '_count') { - return; - } - aggregates[key] = value || 0; - }); - - this.model = new instance.web.Model( - model, fixed_group.__context, fixed_group.__domain); - - var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0; - var leaf_group = fixed_group.__context.group_by.length === 0; - this.attributes = { - grouped_on: grouping_field, - // if terminal group (or no group) and group_by_no_leaf => use group.__count - length: group_size, - value: fixed_group[grouping_field], - // 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']), - - aggregates: aggregates - }; - }, - get: function (key) { - return this.attributes[key]; - }, - subgroups: function () { - return this.model.query().group_by(this.model.context().group_by); - }, - query: function () { - return this.model.query.apply(this.model, arguments); - } - }) -}; - -instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{ +instance.web.DataGroup = instance.web.CallbackEnabled.extend({ /** * Management interface between views and grouped collections of OpenERP * records. @@ -451,9 +361,9 @@ instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web. * content of the current grouping level. * * @constructs instance.web.DataGroup - * @extends instance.web.OldWidget + * @extends instance.web.CallbackEnabled * - * @param {instance.web.OldWidget} parent widget + * @param {instance.web.CallbackEnabled} parent widget * @param {String} model name of the model managed by this DataGroup * @param {Array} domain search domain for this DataGroup * @param {Object} context context of the DataGroup's searches @@ -471,49 +381,47 @@ instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web. }, list: function (fields, ifGroups, ifRecords) { var self = this; - $.when(this.model.query(fields) - .order_by(this.sort) - .group_by(this.group_by)).then(function (groups) { - if (!groups) { - ifRecords(_.extend( - new instance.web.DataSetSearch( - self, self.model.name, - self.model.context(), - self.model.domain()), - {_sort: self.sort})); + var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by); + $.when(query).then(function (querygroups) { + // leaf node + if (!querygroups) { + var ds = new instance.web.DataSetSearch(self, self.model.name, self.model.context(), self.model.domain()); + ds._sort = self.sort; + ifRecords(ds); return; } - ifGroups(_(groups).map(function (group) { + // internal node + var child_datagroups = _(querygroups).map(function (group) { var child_context = _.extend( {}, self.model.context(), group.model.context()); - return _.extend( - new instance.web.DataGroup( - self, self.model.name, group.model.domain(), - child_context, group.model._context.group_by, - self.level + 1), - { - __context: child_context, - __domain: group.model.domain(), - grouped_on: group.get('grouped_on'), - length: group.get('length'), - value: group.get('value'), - openable: group.get('has_children'), - aggregates: group.get('aggregates') - }, {sort: self.sort}); - })); + var child_dg = new instance.web.DataGroup( + self, self.model.name, group.model.domain(), + child_context, group.model._context.group_by, + self.level + 1); + child_dg.sort = self.sort; + // copy querygroup properties + child_dg.__context = child_context; + child_dg.__domain = group.model.domain(); + child_dg.folded = group.get('folded'); + child_dg.grouped_on = group.get('grouped_on'); + child_dg.length = group.get('length'); + child_dg.value = group.get('value'); + child_dg.openable = group.get('has_children'); + child_dg.aggregates = group.get('aggregates'); + return child_dg; + }); + ifGroups(child_datagroups); }); } }); -instance.web.ContainerDataGroup = instance.web.DataGroup.extend({ }); -instance.web.GrouplessDataGroup = instance.web.DataGroup.extend({ }); -instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ { +instance.web.StaticDataGroup = instance.web.DataGroup.extend({ /** - * A specialization of groupless data groups, relying on a single static + * A specialization of data groups, relying on a single static * dataset as its records provider. * * @constructs instance.web.StaticDataGroup - * @extends instance.web.GrouplessDataGroup + * @extends instance.web.DataGroup * @param {openep.web.DataSetStatic} dataset a static dataset backing the groups */ init: function (dataset) { @@ -524,13 +432,12 @@ instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lend } }); -instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{ +instance.web.DataSet = instance.web.CallbackEnabled.extend({ /** - * DateaManagement interface between views and the collection of selected - * OpenERP records (represents the view's state?) + * Collection of OpenERP records, used to share records and the current selection between views. * * @constructs instance.web.DataSet - * @extends instance.web.OldWidget + * @extends instance.web.CallbackEnabled * * @param {String} model the OpenERP model this dataset will manage */ @@ -644,15 +551,10 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * Creates a new record in db * * @param {Object} data field values to set on the new record - * @param {Function} callback function called with operation result - * @param {Function} error_callback function called in case of creation error * @returns {$.Deferred} */ - create: function(data, callback, error_callback) { - return this._model.call('create', - [data], {context: this._model.context()}) - .pipe(function (r) { return {result: r}; }) - .then(callback, error_callback); + create: function(data) { + return this._model.call('create', [data], {context: this._model.context()}); }, /** * Saves the provided data in an existing db record @@ -663,24 +565,17 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} error_callback function called in case of write error * @returns {$.Deferred} */ - write: function (id, data, options, callback, error_callback) { + write: function (id, data, options) { options = options || {}; - return this._model.call('write', - [[id], data], {context: this._model.context(options.context)}) - .pipe(function (r) { return {result: r}}) - .then(callback, error_callback); + return this._model.call('write', [[id], data], {context: this._model.context(options.context)}); }, /** * Deletes an existing record from the database * * @param {Number|String} ids identifier of the record to delete - * @param {Function} callback function called with operation result - * @param {Function} error_callback function called in case of deletion error */ - unlink: function(ids, callback, error_callback) { - return this._model.call('unlink', - [ids], {context: this._model.context()}) - .then(callback, error_callback); + unlink: function(ids) { + return this._model.call('unlink', [ids], {context: this._model.context()}); }, /** * Calls an arbitrary RPC method @@ -701,43 +596,35 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Array} [args] * @param {Number} [domain_index] index of a domain to evaluate in the args array * @param {Number} [context_index] index of a context to evaluate in the args array - * @param {Function} callback - * @param {Function} error_callback * @returns {$.Deferred} */ - call_and_eval: function (method, args, domain_index, context_index, callback, error_callback) { - return this.rpc('/web/dataset/call', { + call_and_eval: function (method, args, domain_index, context_index) { + return instance.session.rpc('/web/dataset/call', { model: this.model, method: method, domain_id: domain_index == undefined ? null : domain_index, context_id: context_index == undefined ? null : context_index, args: args || [] - }, callback, error_callback); + }); }, /** * Calls a button method, usually returning some sort of action * * @param {String} method * @param {Array} [args] - * @param {Function} callback - * @param {Function} error_callback * @returns {$.Deferred} */ - call_button: function (method, args, callback, error_callback) { - return this._model.call_button(method, args) - .then(callback, error_callback); + call_button: function (method, args) { + return this._model.call_button(method, args); }, /** * Fetches the "readable name" for records, based on intrinsic rules * * @param {Array} ids - * @param {Function} callback * @returns {$.Deferred} */ - name_get: function(ids, callback) { - return this._model.call('name_get', - [ids], {context: this._model.context()}) - .then(callback); + name_get: function(ids) { + return this._model.call('name_get', [ids], {context: this._model.context()}); }, /** * @@ -748,28 +635,24 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} callback function to call with name_search result * @returns {$.Deferred} */ - name_search: function (name, domain, operator, limit, callback) { + name_search: function (name, domain, operator, limit) { return this._model.call('name_search', { name: name || '', args: domain || false, operator: operator || 'ilike', context: this._model.context(), limit: limit || 0 - }).then(callback); + }); }, /** * @param name - * @param callback */ - name_create: function(name, callback) { - return this._model.call('name_create', - [name], {context: this._model.context()}) - .then(callback); + name_create: function(name) { + return this._model.call('name_create', [name], {context: this._model.context()}); }, - exec_workflow: function (id, signal, callback) { + exec_workflow: function (id, signal) { return this._model.exec_workflow(id, signal) - .pipe(function (result) { return { result: result }; }) - .then(callback); + .pipe(function (result) { return { result: result }; }); }, get_context: function(request_context) { return this._model.context(request_context); @@ -805,7 +688,24 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da alter_ids: function(n_ids) { this.ids = n_ids; }, + /** + * Resequence records. + * + * @param {Array} ids identifiers of the records to resequence + * @returns {$.Deferred} + */ + resequence: function (ids, options) { + options = options || {}; + return instance.session.rpc('/web/dataset/resequence', { + model: this.model, + ids: ids, + context: this._model.context(options.context), + }).pipe(function (results) { + return results; + }); + }, }); + instance.web.DataSetStatic = instance.web.DataSet.extend({ init: function(parent, model, context, ids) { this._super(parent, model, context); @@ -836,7 +736,8 @@ instance.web.DataSetStatic = instance.web.DataSet.extend({ this.set_ids(_.without.apply(null, [this.ids].concat(ids))); } }); -instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{ + +instance.web.DataSetSearch = instance.web.DataSet.extend({ /** * @constructs instance.web.DataSetSearch * @extends instance.web.DataSet @@ -886,7 +787,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web }, unlink: function(ids, callback, error_callback) { var self = this; - return this._super(ids, function(result) { + return this._super(ids).then(function(result) { self.ids = _(self.ids).difference(ids); if (self._length) { self._length -= 1; @@ -895,9 +796,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web self.index = self.index <= self.ids.length - 1 ? self.index : (self.ids.length > 0 ? self.ids.length -1 : 0); } - if (callback) - callback(result); - }, error_callback); + }); }, size: function () { if (this._length !== undefined) { @@ -906,6 +805,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web return this._super(); } }); + instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ virtual_id_prefix: "one2many_v_id_", debug_mode: true, @@ -920,16 +820,14 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ on_default_get: function(res) { this.last_default_get = res; }, - create: function(data, callback, error_callback) { + create: function(data) { var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data, defaults: this.last_default_get}; this.to_create.push(_.extend(_.clone(cached), {values: _.clone(cached.values)})); this.cache.push(cached); - var prom = $.Deferred().then(callback); - prom.resolve({result: cached.id}); - return prom.promise(); + return $.Deferred().resolve(cached.id).promise(); }, - write: function (id, data, options, callback) { + write: function (id, data, options) { var self = this; var record = _.detect(this.to_create, function(x) {return x.id === id;}); record = record || _.detect(this.to_write, function(x) {return x.id === id;}); @@ -955,9 +853,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ $.extend(cached.values, record.values); if (dirty) this.on_change(); - var to_return = $.Deferred().then(callback); - to_return.resolve({result: true}); - return to_return.promise(); + return $.Deferred().resolve(true).promise(); }, unlink: function(ids, callback, error_callback) { var self = this; @@ -981,7 +877,8 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ this.cache = []; this.delete_all = false; }, - on_change: function() {}, + on_change: function() { + }, read_ids: function (ids, fields, options) { var self = this; var to_get = []; @@ -1053,7 +950,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ } return completion.promise(); }, - call_button: function (method, args, callback, error_callback) { + call_button: function (method, args) { var id = args[0][0], index; for(var i=0, len=this.cache.length; i<len; ++i) { var record = this.cache[i]; @@ -1064,7 +961,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ break; } } - return this._super(method, args, callback, error_callback); + return this._super(method, args); }, alter_ids: function(n_ids) { this._super(n_ids); @@ -1096,23 +993,23 @@ instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({ return this._super.apply(this, arguments); } }, - create: function(data, callback, error_callback) { + create: function(data) { if (this.create_function) { - return this.create_function(data, this._super).then(callback, error_callback); + return this.create_function(data, this._super); } else { return this._super.apply(this, arguments); } }, - write: function (id, data, options, callback, error_callback) { + write: function (id, data, options) { if (this.write_function) { - return this.write_function(id, data, options, this._super).then(callback, error_callback); + return this.write_function(id, data, options, this._super); } else { return this._super.apply(this, arguments); } }, - unlink: function(ids, callback, error_callback) { + unlink: function(ids) { if (this.unlink_function) { - return this.unlink_function(ids, this._super).then(callback, error_callback); + return this.unlink_function(ids, this._super); } else { return this._super.apply(this, arguments); } @@ -1165,7 +1062,7 @@ instance.web.CompoundDomain = instance.web.Class.extend({ } }); -instance.web.DropMisordered = instance.web.Class.extend(/** @lends openerp.web.DropMisordered# */{ +instance.web.DropMisordered = instance.web.Class.extend({ /** * @constructs instance.web.DropMisordered * @extends instance.web.Class diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js index f8e669e1b4a..1353f59d4a5 100644 --- a/addons/web/static/src/js/data_export.js +++ b/addons/web/static/src/js/data_export.js @@ -5,7 +5,15 @@ instance.web.DataExport = instance.web.Dialog.extend({ template: 'ExportTreeView', dialog_title: {toString: function () { return _t("Export Data"); }}, init: function(parent, dataset) { - this._super(parent); + var self = this; + options = { + buttons : [ + {text: _t("Close"), click: function() { self.close(); }}, + {text: _t("Export To File"), click: function() { self.on_click_export_data(); }} + ], + close: function(event, ui){ self.close();} + } + this._super(parent, options); this.records = {}; this.dataset = dataset; this.exports = new instance.web.DataSetSearch( @@ -14,38 +22,32 @@ instance.web.DataExport = instance.web.Dialog.extend({ start: function() { var self = this; this._super.apply(this, arguments); - this.open({ - buttons : [ - {text: _t("Close"), click: function() { self.close(); }}, - {text: _t("Export To File"), click: function() { self.on_click_export_data(); }} - ], - close: function(event, ui){ self.close();} - }); - self.$element.removeClass('ui-dialog-content ui-widget-content'); - self.$element.find('#add_field').click(function() { + this.open(); + self.$el.removeClass('ui-dialog-content ui-widget-content'); + self.$el.find('#add_field').click(function() { if ($('#field-tree-structure tr.ui-selected')) { - var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a'); + var fld = self.$el.find('#field-tree-structure tr.ui-selected').find('a'); for (var i = 0; i < fld.length; i++) { var id = $(fld[i]).attr('id').split('-')[1]; var string = $(fld[i]).attr('string'); self.add_field(id, string); } - self.$element.find('#field-tree-structure tr').removeClass('ui-selected'); + self.$el.find('#field-tree-structure tr').removeClass('ui-selected'); } }); - self.$element.find('#remove_field').click(function() { - self.$element.find('#fields_list option:selected').remove(); + self.$el.find('#remove_field').click(function() { + self.$el.find('#fields_list option:selected').remove(); }); - self.$element.find('#remove_all_field').click(function() { - self.$element.find('#fields_list').empty(); + self.$el.find('#remove_all_field').click(function() { + self.$el.find('#fields_list').empty(); }); - this.$element.find('#export_new_list').click(this.on_show_save_list); + this.$el.find('#export_new_list').click(this.on_show_save_list); var got_fields = new $.Deferred(); - this.$element.find('#import_compat').change(function() { - self.$element.find('#fields_list').empty(); - self.$element.find('#field-tree-structure').remove(); - var import_comp = self.$element.find("#import_compat").val(); + this.$el.find('#import_compat').change(function() { + self.$el.find('#fields_list').empty(); + self.$el.find('#field-tree-structure').remove(); + var import_comp = self.$el.find("#import_compat").val(); self.rpc("/web/export/get_fields", { model: self.dataset.model, import_compat: Boolean(import_comp) @@ -61,7 +63,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ this.show_exports_list()); }, do_setup_export_formats: function (formats) { - var $fmts = this.$element.find('#export_format'); + var $fmts = this.$el.find('#export_format'); _(formats).each(function (format) { var opt = new Option(format.label, format.tag); if (format.error) { @@ -76,8 +78,8 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, show_exports_list: function() { var self = this; - if (self.$element.find('#saved_export_list').is(':hidden')) { - self.$element.find('#ExistsExportList').show(); + if (self.$el.find('#saved_export_list').is(':hidden')) { + self.$el.find('#ExistsExportList').show(); return; } return this.exports.read_slice(['name'], { @@ -86,35 +88,35 @@ instance.web.DataExport = instance.web.Dialog.extend({ if (!export_list.length) { return; } - self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list})); - self.$element.find('#saved_export_list').change(function() { - self.$element.find('#fields_list option').remove(); - var export_id = self.$element.find('#saved_export_list option:selected').val(); + self.$el.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list})); + self.$el.find('#saved_export_list').change(function() { + self.$el.find('#fields_list option').remove(); + var export_id = self.$el.find('#saved_export_list option:selected').val(); if (export_id) { self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field); } }); - self.$element.find('#delete_export_list').click(function() { - var select_exp = self.$element.find('#saved_export_list option:selected'); + self.$el.find('#delete_export_list').click(function() { + var select_exp = self.$el.find('#saved_export_list option:selected'); if (select_exp.val()) { self.exports.unlink([parseInt(select_exp.val(), 10)]); select_exp.remove(); - if (self.$element.find('#saved_export_list option').length <= 1) { - self.$element.find('#ExistsExportList').hide(); + if (self.$el.find('#saved_export_list option').length <= 1) { + self.$el.find('#ExistsExportList').hide(); } } }); }); }, do_load_export_field: function(field_list) { - var export_node = this.$element.find("#fields_list"); + var export_node = this.$el.find("#fields_list"); _(field_list).each(function (field) { export_node.append(new Option(field.label, field.name)); }); }, on_show_save_list: function() { var self = this; - var current_node = self.$element.find("#savenewlist"); + var current_node = self.$el.find("#savenewlist"); if (!(current_node.find("label")).length) { current_node.append(QWeb.render('ExportNewList')); current_node.find("#add_export_list").click(function() { @@ -150,14 +152,14 @@ instance.web.DataExport = instance.web.Dialog.extend({ if (!export_list_id.result) { return; } - self.$element.find("#saved_export_list").append( + self.$el.find("#saved_export_list").append( new Option(value, export_list_id.result)); - if (self.$element.find("#saved_export_list").is(":hidden")) { + if (self.$el.find("#saved_export_list").is(":hidden")) { self.show_exports_list(); } }); this.on_show_save_list(); - this.$element.find("#fields_list option").remove(); + this.$el.find("#fields_list option").remove(); }, on_click: function(id, record) { var self = this; @@ -173,7 +175,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ } if (!record.loaded) { - var import_comp = self.$element.find("#import_compat").val(); + var import_comp = self.$el.find("#import_compat").val(); self.rpc("/web/export/get_fields", { model: model, prefix: prefix, @@ -191,44 +193,44 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, on_show_data: function(result, after) { var self = this; - var imp_cmpt = Boolean(self.$element.find("#import_compat").val()); + var imp_cmpt = Boolean(self.$el.find("#import_compat").val()); if (after) { - var current_tr = self.$element.find("tr[id='treerow-" + after + "']"); + var current_tr = self.$el.find("tr[id='treerow-" + after + "']"); current_tr.addClass('open'); current_tr.find('img').attr('src','/web/static/src/img/collapse.gif'); current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result})); } else { - self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result})); + self.$el.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result})); } _.each(result, function(record) { self.records[record.id] = record.value; if (record.required) { - var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + var required_fld = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); required_fld.addClass("oe_export_requiredfield"); } - self.$element.find("img[id='parentimg-" + record.id +"']").click(function() { + self.$el.find("img[id='parentimg-" + record.id +"']").click(function() { self.on_click(this.id, record); }); - self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) { + self.$el.find("tr[id='treerow-" + record.id + "']").click(function(e) { if (e.shiftKey) { var frst_click, scnd_click = ''; if (self.row_index == 0) { self.row_index = this.rowIndex; - frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1]; + frst_click = self.$el.find("tr[id^='treerow-']")[self.row_index-1]; $(frst_click).addClass("ui-selected"); } else { if (this.rowIndex >=self.row_index) { for (var i = (self.row_index-1); i < this.rowIndex; i++) { - scnd_click = self.$element.find("tr[id^='treerow-']")[i]; + scnd_click = self.$el.find("tr[id^='treerow-']")[i]; if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) { $(scnd_click).addClass("ui-selected"); } } } else { for (var i = (self.row_index-1); i >= (this.rowIndex-1); i--) { - scnd_click = self.$element.find("tr[id^='treerow-']")[i]; + scnd_click = self.$el.find("tr[id^='treerow-']")[i]; if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) { $(scnd_click).addClass("ui-selected"); } @@ -238,10 +240,10 @@ instance.web.DataExport = instance.web.Dialog.extend({ } self.row_index = this.rowIndex; - self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function() { + self.$el.find("tr[id='treerow-" + record.id + "']").keyup(function() { self.row_index = 0; }); - var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + var o2m_selection = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); if ($(o2m_selection).hasClass("oe_export_readonlyfield")) { return false; } @@ -252,14 +254,14 @@ instance.web.DataExport = instance.web.Dialog.extend({ $(this).addClass('ui-selected').find('a').focus(); } } else if (!e.shiftKey) { - self.$element.find("tr.ui-selected") + self.$el.find("tr.ui-selected") .removeClass("ui-selected").find('a').blur(); $(this).addClass("ui-selected").find('a').focus(); } return false; }); - self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) { + self.$el.find("tr[id='treerow-" + record.id + "']").keydown(function(e) { var keyCode = e.keyCode || e.which; var arrow = {left: 37, up: 38, right: 39, down: 40 }; switch (keyCode) { @@ -297,19 +299,19 @@ instance.web.DataExport = instance.web.Dialog.extend({ break; } }); - self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function() { - var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column'); + self.$el.find("tr[id='treerow-" + record.id + "']").dblclick(function() { + var $o2m_selection = self.$el.find("tr[id^='treerow-" + record.id + "']").find('#tree-column'); if (!$o2m_selection.hasClass("oe_export_readonlyfield")) { self.add_field(record.id, $(this).find("a").attr("string")); } }); }); - self.$element.find('#fields_list').mouseover(function(event) { + self.$el.find('#fields_list').mouseover(function(event) { if (event.relatedTarget) { if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) { var field_id = event.relatedTarget.attributes["id"]["value"]; if (field_id && field_id.split("-")[0] === 'export') { - if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) { + if (!self.$el.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) { self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]); } } @@ -319,7 +321,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, showcontent: function(id) { // show & hide the contents - var $this = this.$element.find("tr[id='treerow-" + id + "']"); + var $this = this.$el.find("tr[id='treerow-" + id + "']"); var is_open = $this.hasClass('open'); $this.toggleClass('open'); @@ -329,7 +331,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ } else { first_child.attr('src', '/web/static/src/img/collapse.gif'); } - var child_field = this.$element.find("tr[id^='treerow-" + id +"/']"); + var child_field = this.$el.find("tr[id^='treerow-" + id +"/']"); var child_len = (id.split("/")).length + 1; for (var i = 0; i < child_field.length; i++) { var $child = $(child_field[i]); @@ -345,15 +347,15 @@ instance.web.DataExport = instance.web.Dialog.extend({ } }, add_field: function(field_id, string) { - var field_list = this.$element.find('#fields_list'); - if (this.$element.find("#fields_list option[value='" + field_id + "']") - && !this.$element.find("#fields_list option[value='" + field_id + "']").length) { + var field_list = this.$el.find('#fields_list'); + if (this.$el.find("#fields_list option[value='" + field_id + "']") + && !this.$el.find("#fields_list option[value='" + field_id + "']").length) { field_list.append(new Option(string, field_id)); } }, get_fields: function() { var export_field = []; - this.$element.find("#fields_list option").each(function() { + this.$el.find("#fields_list option").each(function() { export_field.push($(this).val()); }); if (!export_field.length) { @@ -363,7 +365,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, on_click_export_data: function() { var self = this; - var exported_fields = this.$element.find('#fields_list option').map(function () { + var exported_fields = this.$el.find('#fields_list option').map(function () { // DOM property is textContent, but IE8 only knows innerText return {name: self.records[this.value] || this.value, label: this.textContent || this.innerText}; @@ -375,7 +377,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ } exported_fields.unshift({name: 'id', label: 'External ID'}); - var export_format = this.$element.find("#export_format").val(); + var export_format = this.$el.find("#export_format").val(); instance.web.blockUI(); this.session.get_file({ url: '/web/export/' + export_format, @@ -385,13 +387,13 @@ instance.web.DataExport = instance.web.Dialog.extend({ ids: this.dataset.ids, domain: this.dataset.domain, import_compat: Boolean( - this.$element.find("#import_compat").val()) + this.$el.find("#import_compat").val()) })}, complete: instance.web.unblockUI }); }, close: function() { - this.$element.remove(); + this.$el.remove(); this._super(); } }); diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js deleted file mode 100644 index 620878569ce..00000000000 --- a/addons/web/static/src/js/data_import.js +++ /dev/null @@ -1,393 +0,0 @@ -openerp.web.data_import = function(instance) { -var QWeb = instance.web.qweb, - _t = instance.web._t; -/** - * Safari does not deal well at all with raw JSON data being returned. As a - * result, we're going to cheat by using a pseudo-jsonp: instead of getting - * JSON data in the iframe, we're getting a ``script`` tag which consists of a - * function call and the returned data (the json dump). - * - * The function is an auto-generated name bound to ``window``, which calls - * back into the callback provided here. - * - * @param {Object} form the form element (DOM or jQuery) to use in the call - * @param {Object} attributes jquery.form attributes object - * @param {Function} callback function to call with the returned data - */ -function jsonp(form, attributes, callback) { - attributes = attributes || {}; - var options = {jsonp: _.uniqueId('import_callback_')}; - window[options.jsonp] = function () { - delete window[options.jsonp]; - callback.apply(null, arguments); - }; - if ('data' in attributes) { - _.extend(attributes.data, options); - } else { - _.extend(attributes, {data: options}); - } - $(form).ajaxSubmit(attributes); -} - -instance.web.DataImport = instance.web.Dialog.extend({ - template: 'ImportDataView', - dialog_title: {toString: function () { return _t("Import Data"); }}, - init: function(parent, dataset){ - var self = this; - this._super(parent, {}); - this.model = parent.model; - this.fields = []; - this.all_fields = []; - this.fields_with_defaults = []; - this.required_fields = null; - - var convert_fields = function (root, prefix) { - prefix = prefix || ''; - _(root.fields).each(function (f) { - self.all_fields.push(prefix + f.name); - if (f.fields) { - convert_fields(f, prefix + f.id + '/'); - } - }); - }; - this.ready = $.Deferred.queue().then(function () { - self.required_fields = _(self.fields).chain() - .filter(function (field) { - return field.required && - !_.include(self.fields_with_defaults, field.id); }) - .pluck('id') - .uniq() - .value(); - convert_fields(self); - self.all_fields.sort(); - }); - }, - start: function() { - var self = this; - this._super(); - this.open({ - buttons: [ - {text: _t("Close"), click: function() { self.destroy(); }}, - {text: _t("Import File"), click: function() { self.do_import(); }, 'class': 'oe_import_dialog_button'} - ], - close: function(event, ui) { - self.destroy(); - } - }); - this.toggle_import_button(false); - this.$element.find('#csvfile').change(this.on_autodetect_data); - this.$element.find('fieldset').change(this.on_autodetect_data); - this.$element.delegate('fieldset legend', 'click', function() { - $(this).parent().toggleClass('oe_closed'); - }); - this.ready.push(new instance.web.DataSet(this, this.model).call( - 'fields_get', [], function (fields) { - self.graft_fields(fields); - self.ready.push(new instance.web.DataSet(self, self.model) - .default_get(_.pluck(self.fields, 'id')).then(function (fields) { - _.each(fields, function(val, key) { - if (val) { - self.fields_with_defaults.push(key); - } - }); - }) - ) - })); - }, - graft_fields: function (fields, parent, level) { - parent = parent || this; - level = level || 0; - - var self = this; - if (level === 0) { - parent.fields.push({ - id: 'id', - name: 'id', - string: _t('External ID'), - required: false - }); - } - _(fields).each(function (field, field_name) { - // Ignore spec for id field - // Don't import function fields (function and related) - if (field_name === 'id') { - return; - } - // Skip if there's no state which could disable @readonly, - // if a field is ever always readonly we can't import it - if (field.readonly) { - // no states at all - if (_.isEmpty(field.states)) { return; } - // no state altering @readonly - if (!_.any(field.states, function (modifiers) { - return _(modifiers).chain().pluck(0).contains('readonly').value(); - })) { return; } - } - - var f = { - id: field_name, - name: field_name, - string: field.string, - required: field.required - }; - - switch (field.type) { - case 'many2many': - case 'many2one': - // push a copy for the bare many2one field, to allow importing - // using name_search too - even if we default to exporting the XML ID - var many2one_field = _.extend({}, f) - parent.fields.push(many2one_field); - f.name += '/id'; - break; - case 'one2many': - f.name += '/id'; - f.fields = []; - // only fetch sub-fields to a depth of 2 levels - if (level < 2) { - self.ready.push(new instance.web.DataSet(self, field.relation).call( - 'fields_get', [], function (fields) { - self.graft_fields(fields, f, level+1); - })); - } - break; - } - parent.fields.push(f); - }); - }, - toggle_import_button: function (newstate) { - instance.web.dialog(this.$element, 'widget') - .find('.oe_import_dialog_button') - .button('option', 'disabled', !newstate); - }, - do_import: function() { - if(!this.$element.find('#csvfile').val()) { return; } - var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10); - var with_headers = this.$element.find('#file_has_headers').prop('checked'); - if (!lines_to_skip && with_headers) { - lines_to_skip = 1; - } - var indices = [], fields = []; - this.$element.find(".sel_fields").each(function(index, element) { - var val = element.value; - if (!val) { - return; - } - indices.push(index); - fields.push(val); - }); - - jsonp(this.$element.find('#import_data'), { - url: '/web/import/import_data', - data: { - model: this.model, - meta: JSON.stringify({ - skip: lines_to_skip, - indices: indices, - fields: fields - }) - } - }, this.on_import_results); - }, - on_autodetect_data: function() { - if(!this.$element.find('#csvfile').val()) { return; } - jsonp(this.$element.find('#import_data'), { - url: '/web/import/detect_data' - }, this.on_import_results); - }, - on_import_results: function(results) { - this.$element.find('#result').empty(); - var headers, result_node = this.$element.find("#result"); - - if (results['error']) { - result_node.append(QWeb.render('ImportView.error', { - 'error': results['error']})); - this.$element.find('fieldset').removeClass('oe_closed'); - return; - } - if (results['success']) { - if (this.getParent().getParent().active_view == "list") { - this.getParent().reload_content(); - } - this.destroy(); - return; - } - - if (results['records']) { - var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10), - with_headers = this.$element.find('#file_has_headers').prop('checked'); - headers = with_headers ? results.records[0] : null; - - result_node.append(QWeb.render('ImportView.result', { - 'headers': headers, - 'records': lines_to_skip ? results.records.slice(lines_to_skip) - : with_headers ? results.records.slice(1) - : results.records - })); - this.$element.find('fieldset').addClass('oe_closed'); - } - this.$element.find('form').removeClass('oe_import_no_result'); - - this.$element.delegate('.oe_m2o_drop_down_button', 'click', function () { - $(this).prev('input').focus(); - }); - - var self = this; - this.ready.then(function () { - var $fields = self.$element.find('.sel_fields').bind('blur', function () { - if (this.value && !_(self.all_fields).contains(this.value)) { - this.value = ''; - } - }).autocomplete({ - minLength: 0, - source: self.all_fields, - change: self.on_check_field_values - }).focus(function () { - $(this).autocomplete('search'); - }); - // Column auto-detection - _(headers).each(function (header, index) { - var field_name = self.match_column_to_field(header); - if (field_name) { - $fields.eq(index).val(field_name); - } - }); - self.on_check_field_values(); - }); - }, - /** - * Returns the name of the field (nested) matching the provided column name - * - * @param {String} name column name to look for - * @param {Array} [fields] fields to look into for the provided name - * @returns {String|undefined} - */ - match_column_to_field: function (name, fields) { - fields = fields || this.fields; - var f; - f = _(fields).detect(function (field) { - return field.name === name - }); - if (!f) { - f = _(fields).detect(function (field) { - // TODO: levenshtein between header and field.string - return field.string.toLowerCase() === name.toLowerCase(); - }); - } - if (f) { return f.name; } - - // if ``name`` is a path (o2m), we need to recurse through its .fields - var index = name.indexOf('/'); - if (index === -1) { return undefined; } - // Get the first path section, try to find the matching field - var column_name = name.substring(0, index); - f = _(fields).detect(function (field) { - // field.name for o2m is $foo/id, so we want to match on id - return field.id === column_name; - }); - if (!f) { - f = _(fields).detect(function (field) { - return field.string.toLowerCase() === column_name.toLowerCase(); - }); - } - if (!f) { return undefined; } - - // if we found a matching field for the first path section, recurse in - // its own .fields to try and get the rest of the path matched - var rest = this.match_column_to_field( - name.substring(index+1), f.fields); - if (!rest) { return undefined; } - return f.id + '/' + rest; - }, - /** - * Looks through all the field selections, and tries to find if two - * (or more) columns were matched to the same model field. - * - * Returns a map of the multiply-mapped fields to an array of offending - * columns (not actually columns, but the inputs containing the same field - * names). - * - * Also has the side-effect of marking the discovered inputs with the class - * ``duplicate_fld``. - * - * @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs - */ - find_duplicate_fields: function() { - // Maps values to DOM nodes, in order to discover duplicates - var values = {}, duplicates = {}; - this.$element.find(".sel_fields").each(function(index, element) { - var value = element.value; - var $element = $(element).removeClass('duplicate_fld'); - if (!value) { return; } - - if (!(value in values)) { - values[value] = element; - } else { - var same_valued_field = values[value]; - if (value in duplicates) { - duplicates[value].push(element); - } else { - duplicates[value] = [same_valued_field, element]; - } - $element.add(same_valued_field).addClass('duplicate_fld'); - } - }); - return duplicates; - }, - on_check_field_values: function () { - this.$element.find("#message, #msg").remove(); - - var required_valid = this.check_required(); - - var duplicates = this.find_duplicate_fields(); - if (_.isEmpty(duplicates)) { - this.toggle_import_button(required_valid); - } else { - var $err = $('<div id="msg" style="color: red;">'+_t("Destination fields should only be selected once, some fields are selected more than once:")+'</div>').insertBefore(this.$element.find('#result')); - var $dupes = $('<dl>').appendTo($err); - _(duplicates).each(function(elements, value) { - $('<dt>').text(value).appendTo($dupes); - _(elements).each(function(element) { - var cell = $(element).closest('td'); - $('<dd>').text(cell.parent().children().index(cell)).appendTo($dupes); - }); - }); - this.toggle_import_button(false); - } - - }, - check_required: function() { - var self = this; - if (!self.required_fields.length) { return true; } - - // Resolve field id based on column name, as there may be - // several ways to provide the value for a given field and - // thus satisfy the requirement. - // (e.g. m2o_id or m2o_id/id columns may be provided) - var resolve_field_id = function(column_name) { - var f = _.detect(self.fields, function(field) { - return field.name === column_name; - }); - if (!f) { return column_name; }; - return f.id; - }; - - var selected_fields = _(this.$element.find('.sel_fields').get()).chain() - .pluck('value') - .compact() - .map(resolve_field_id) - .value(); - - var missing_fields = _.difference(this.required_fields, selected_fields); - if (missing_fields.length) { - this.$element.find("#result").before('<div id="message" style="color:red">' + _t("*Required Fields are not selected :") + missing_fields + '.</div>'); - return false; - } - return true; - }, - destroy: function() { - this.$element.remove(); - this._super(); - } -}); -}; diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 6ceb7090e44..3e5e6fecd5e 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -138,6 +138,9 @@ instance.web.format_value = function (value, descriptor, value_if_empty) { return value[1]; case 'one2many': case 'many2many': + if (typeof value === 'string') { + return value; + } return _.str.sprintf(_t("(%d records)"), value.length); case 'datetime': if (typeof(value) == "string") @@ -271,82 +274,4 @@ instance.web.auto_date_to_str = function(value, type) { } }; -/** - * Formats a provided cell based on its field type. Most of the field types - * return a correctly formatted value, but some tags and fields are - * special-cased in their handling: - * - * * buttons will return an actual ``<button>`` tag with a bunch of error handling - * - * * boolean fields will return a checkbox input, potentially disabled - * - * * binary fields will return a link to download the binary data as a file - * - * @param {Object} row_data record whose values should be displayed in the cell - * @param {Object} column column descriptor - * @param {"button"|"field"} column.tag base control type - * @param {String} column.type widget type for a field control - * @param {String} [column.string] button label - * @param {String} [column.icon] button icon - * @param {Object} [options] - * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false`` - * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ? - * @param {String} [options.model] current record's model - * @param {Number} [options.id] current record's id - * - */ -instance.web.format_cell = function (row_data, column, options) { - options = options || {}; - var attrs = {}; - if (options.process_modifiers !== false) { - attrs = column.modifiers_for(row_data); - } - if (attrs.invisible) { return ''; } - - if (column.tag === 'button') { - return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' + - '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' + - '</button>', { - title: column.string || '', - additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ? - 'disabled="disabled" class="oe_list_button_disabled"' : '', - prefix: instance.connection.prefix, - icon: column.icon, - alt: column.string || '' - }); - } - if (!row_data[column.id]) { - return options.value_if_empty === undefined ? '' : options.value_if_empty; - } - - switch (column.widget || column.type) { - case "boolean": - return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>', - row_data[column.id].value ? 'checked="checked"' : ''); - case "binary": - var text = _t("Download"), - download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id); - if (column.filename) { - download_url += '&filename_field=' + column.filename; - if (row_data[column.filename]) { - text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value( - row_data[column.filename].value, {type: 'char'})); - } - } - return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', { - text: text, - href: download_url, - size: row_data[column.id].value - }); - case 'progressbar': - return _.template( - '<progress value="<%-value%>" max="100"><%-value%>%</progress>', { - value: _.str.sprintf("%.0f", row_data[column.id].value || 0) - }); - } - - return _.escape(instance.web.format_value( - row_data[column.id].value, column, options.value_if_empty)); -} - }; diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 53daa40ae31..71e955e80a6 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -126,21 +126,21 @@ my.InputView = instance.web.Widget.extend({ template: 'SearchView.InputView', start: function () { var p = this._super.apply(this, arguments); - this.$element.on('focus', this.proxy('onFocus')); - this.$element.on('blur', this.proxy('onBlur')); - this.$element.on('keydown', this.proxy('onKeydown')); + this.$el.on('focus', this.proxy('onFocus')); + this.$el.on('blur', this.proxy('onBlur')); + this.$el.on('keydown', this.proxy('onKeydown')); return p; }, onFocus: function () { this.trigger('focused', this); }, onBlur: function () { - this.$element.text(''); + this.$el.text(''); this.trigger('blurred', this); }, getSelection: function () { // get Text node - var root = this.$element[0].childNodes[0]; + var root = this.$el[0].childNodes[0]; if (!root || !root.textContent) { // if input does not have a child node, or the child node is an // empty string, then the selection can only be (0, 0) @@ -202,7 +202,7 @@ my.InputView = instance.web.Widget.extend({ break; case $.ui.keyCode.RIGHT: sel = this.getSelection(); - var len = this.$element.text().length; + var len = this.$el.text().length; if (sel.start !== len || sel.start !== sel.end) { e.stopPropagation(); } @@ -223,17 +223,17 @@ my.FacetView = instance.web.Widget.extend({ }, start: function () { var self = this; - this.$element.on('focus', function () { self.trigger('focused', self); }); - this.$element.on('blur', function () { self.trigger('blurred', self); }); - this.$element.on('click', function (e) { + this.$el.on('focus', function () { self.trigger('focused', self); }); + this.$el.on('blur', function () { self.trigger('blurred', self); }); + this.$el.on('click', function (e) { if ($(e.target).is('.oe_facet_remove')) { self.model.destroy(); return false; } - self.$element.focus(); + self.$el.focus(); e.stopPropagation(); }); - this.$element.on('keydown', function (e) { + this.$el.on('keydown', function (e) { var keys = $.ui.keyCode; switch (e.which) { case keys.BACKSPACE: @@ -242,7 +242,7 @@ my.FacetView = instance.web.Widget.extend({ return false; } }); - var $e = self.$element.find('> span:last-child'); + var $e = self.$el.find('> span:last-child'); var q = $.when(this._super()); return q.pipe(function () { var values = self.model.values.map(function (value) { @@ -253,7 +253,7 @@ my.FacetView = instance.web.Widget.extend({ }); }, model_changed: function () { - this.$element.text(this.$element.text() + '*'); + this.$el.text(this.$el.text() + '*'); } }); my.FacetValueView = instance.web.Widget.extend({ @@ -268,7 +268,7 @@ my.FacetValueView = instance.web.Widget.extend({ this._super(); }, model_changed: function () { - this.$element.text(this.$element.text() + '*'); + this.$el.text(this.$el.text() + '*'); } }); @@ -313,7 +313,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea .on('add change reset remove', this.proxy('renderFacets')); if (this.hidden) { - this.$element.hide(); + this.$el.hide(); } if (this.headless) { this.ready.resolve(); @@ -331,12 +331,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } // Launch a search on clicking the oe_searchview_search button - this.$element.on('click', 'button.oe_searchview_search', function (e) { + this.$el.on('click', 'button.oe_searchview_search', function (e) { e.stopImmediatePropagation(); self.do_search(); }); - this.$element.on('keydown', + this.$el.on('keydown', '.oe_searchview_input, .oe_searchview_facet', function (e) { switch(e.which) { case $.ui.keyCode.LEFT: @@ -350,31 +350,31 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } }); - this.$element.on('click', '.oe_searchview_clear', function (e) { + this.$el.on('click', '.oe_searchview_clear', function (e) { e.stopImmediatePropagation(); self.query.reset(); }); - this.$element.on('click', '.oe_searchview_unfold_drawer', function (e) { + this.$el.on('click', '.oe_searchview_unfold_drawer', function (e) { e.stopImmediatePropagation(); - self.$element.toggleClass('oe_searchview_open_drawer'); + self.$el.toggleClass('oe_searchview_open_drawer'); }); instance.web.bus.on('click', this, function(ev) { if ($(ev.target).parents('.oe_searchview').length === 0) { - self.$element.removeClass('oe_searchview_open_drawer'); + self.$el.removeClass('oe_searchview_open_drawer'); } }); // Focus last input if the view itself is clicked (empty section of // facets element) - this.$element.on('click', function (e) { - if (e.target === self.$element.find('.oe_searchview_facets')[0]) { - self.$element.find('.oe_searchview_input:last').focus(); + this.$el.on('click', function (e) { + if (e.target === self.$el.find('.oe_searchview_facets')[0]) { + self.$el.find('.oe_searchview_input:last').focus(); } }); // when the completion list opens/refreshes, automatically select the // first completion item so if the user just hits [RETURN] or [TAB] it // automatically selects it - this.$element.on('autocompleteopen', function () { - var menu = self.$element.data('autocomplete').menu; + this.$el.on('autocompleteopen', function () { + var menu = self.$el.data('autocomplete').menu; menu.activate( $.Event({ type: "mouseenter" }), menu.element.children().first()); @@ -383,15 +383,15 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return $.when(p, this.ready); }, show: function () { - this.$element.show(); + this.$el.show(); }, hide: function () { - this.$element.hide(); + this.$el.hide(); }, subviewForRoot: function (subview_root) { return _(this.input_subviews).detect(function (subview) { - return subview.$element[0] === subview_root; + return subview.$el[0] === subview_root; }); }, siblingSubview: function (subview, direction, wrap_around) { @@ -406,12 +406,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea focusPreceding: function (subview_root) { return this.siblingSubview( this.subviewForRoot(subview_root), -1, true) - .$element.focus(); + .$el.focus(); }, focusFollowing: function (subview_root) { return this.siblingSubview( this.subviewForRoot(subview_root), +1, true) - .$element.focus(); + .$el.focus(); }, /** @@ -430,19 +430,19 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // autocomplete only correctly handles being initialized on the actual // editable element (and only an element with a @value in 1.8 e.g. - // input or textarea), cheat by setting val() on $element - this.$element.on('keydown', function () { + // input or textarea), cheat by setting val() on $el + this.$el.on('keydown', function () { // keydown is triggered *before* the element's value is set, so // delay this. Pray that setTimeout are executed in FIFO (if they // have the same delay) as autocomplete uses the exact same trick. // FIXME: brittle as fuck setTimeout(function () { - self.$element.val(self.currentInputValue()); + self.$el.val(self.currentInputValue()); }, 0); }); - this.$element.autocomplete({ + this.$el.autocomplete({ source: this.proxy('complete_global_search'), select: this.proxy('select_completion'), focus: function (e) { e.preventDefault(); }, @@ -479,7 +479,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * div[contenteditable].oe_searchview_input) */ currentInputValue: function () { - return this.$element.find('div.oe_searchview_input:focus').text(); + return this.$el.find('div.oe_searchview_input:focus').text(); }, /** * Provide auto-completion result for req.term (an array to `resp`) @@ -509,22 +509,22 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea var input_index = _(this.input_subviews).indexOf( this.subviewForRoot( - this.$element.find('div.oe_searchview_input:focus')[0])); + this.$el.find('div.oe_searchview_input:focus')[0])); this.query.add(ui.item.facet, {at: input_index / 2}); }, childFocused: function () { - this.$element.addClass('oe_focused'); + this.$el.addClass('oe_focused'); }, childBlurred: function () { - var val = this.$element.val(); - this.$element.val(''); - var complete = this.$element.data('autocomplete'); + var val = this.$el.val(); + this.$el.val(''); + var complete = this.$el.data('autocomplete'); if ((val && complete.term === undefined) || complete.previous !== undefined) { throw new Error("new jquery.ui version altering implementation" + " details relied on"); } delete complete.term; - this.$element.removeClass('oe_focused') + this.$el.removeClass('oe_focused') .trigger('blur'); }, /** @@ -538,7 +538,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // _2: undefined if event=change, otherwise model var self = this; var started = []; - var $e = this.$element.find('div.oe_searchview_facets'); + var $e = this.$el.find('div.oe_searchview_facets'); _.invoke(this.input_subviews, 'destroy'); this.input_subviews = []; @@ -569,7 +569,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea input_to_focus = self.input_subviews[(options.at + 1) * 2]; } - input_to_focus.$element.focus(); + input_to_focus.$el.focus(); }); }, @@ -635,6 +635,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return null; } }, + + add_common_inputs: function() { + // add Filters to this.inputs, need view.controls filled + (new instance.web.search.Filters(this)); + // add custom filters to this.inputs + (new instance.web.search.CustomFilters(this)); + // add Advanced to this.inputs + (new instance.web.search.Advanced(this)); + }, + on_loaded: function(data) { var self = this; this.fields_view = data.fields_view; @@ -649,20 +659,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea data.fields_view['arch'].children, data.fields_view.fields); - // add Filters to this.inputs, need view.controls filled - (new instance.web.search.Filters(this)); - // add custom filters to this.inputs - (new instance.web.search.CustomFilters(this)); - // add Advanced to this.inputs - (new instance.web.search.Advanced(this)); + this.add_common_inputs(); // build drawer var drawer_started = $.when.apply( null, _(this.select_for_drawer()).invoke( - 'appendTo', this.$element.find('.oe_searchview_drawer'))); + 'appendTo', this.$el.find('.oe_searchview_drawer'))); - new instance.web.search.AddToReporting(this).appendTo($('.oe_searchview_drawer', this.$element)); - // load defaults var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( 'facet_for_defaults', this.defaults)).then(function () { @@ -677,15 +680,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea */ on_filters_management: function(e) { var self = this; - var select = this.$element.find(".oe_search-view-filters-management"); + var select = this.$el.find(".oe_search-view-filters-management"); var val = select.val(); switch(val) { case 'advanced_filter': this.extended_search.on_activate(); break; - case 'add_to_dashboard': - this.on_add_to_dashboard(); - break; case '': this.do_clear(); } @@ -989,7 +989,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in this.view.query.on('add remove change reset', this.proxy('search_change')); }, start: function () { - this.$element.on('click', 'li', this.proxy('toggle_filter')); + this.$el.on('click', 'li', this.proxy('toggle_filter')); return $.when(null); }, /** @@ -998,7 +998,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in */ search_change: function () { var self = this; - var $filters = this.$element.find('> li').removeClass('oe_selected'); + var $filters = this.$el.find('> li').removeClass('oe_selected'); var facet = this.view.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { @@ -1299,17 +1299,17 @@ instance.web.search.CharField = instance.web.search.Field.extend( /** @lends ins }); instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{ value_from: function () { - if (!this.$element.val()) { + if (!this.$el.val()) { return null; } - var val = this.parse(this.$element.val()), - check = Number(this.$element.val()); + var val = this.parse(this.$el.val()), + check = Number(this.$el.val()); if (isNaN(val) || val !== check) { - this.$element.addClass('error'); + this.$el.addClass('error'); throw new instance.web.search.Invalid( - this.attrs.name, this.$element.val(), this.error_message); + this.attrs.name, this.$el.val(), this.error_message); } - this.$element.removeClass('error'); + this.$el.removeClass('error'); return val; } }); @@ -1493,16 +1493,13 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ if (value.length === 2 && _.isString(value[1])) { return $.when(facet_from(this, value)); } - if (value.length > 1) { - // more than one search_default m2o id? Should we OR them? - throw new Error( - _("M2O search fields do not currently handle multiple default values")); - } + assert(value.length <= 1, + _t("M2O search fields do not currently handle multiple default values")); // there are many cases of {search_default_$m2ofield: [id]}, need // to handle this as if it were a single value. value = value[0]; } - return this.model.call('name_get', [value], {}).pipe(function (names) { + return this.model.call('name_get', [value]).pipe(function (names) { if (_(names).isEmpty()) { return null; } return facet_from(self, names[0]); }) @@ -1542,9 +1539,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ self.clear_selection(); }) .on('reset', this.proxy('clear_selection')); - this.$element.on('submit', 'form', this.proxy('save_current')); - this.$element.on('click', 'h4', function () { - self.$element.toggleClass('oe_opened'); + this.$el.on('submit', 'form', this.proxy('save_current')); + this.$el.on('click', 'h4', function () { + self.$el.toggleClass('oe_opened'); }); // FIXME: local eval of domain and context to get rid of special endpoint return this.rpc('/web/searchview/get_filters', { @@ -1552,7 +1549,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }).pipe(this.proxy('set_filters')); }, clear_selection: function () { - this.$element.find('li.oe_selected').removeClass('oe_selected'); + this.$el.find('li.oe_selected').removeClass('oe_selected'); }, append_filter: function (filter) { var self = this; @@ -1564,7 +1561,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } else { var id = filter.id; $filter = this.filters[key] = $('<li></li>') - .appendTo(this.$element.find('.oe_searchview_custom_list')) + .appendTo(this.$el.find('.oe_searchview_custom_list')) .addClass(filter.user_id ? 'oe_searchview_custom_private' : 'oe_searchview_custom_public') .text(filter.name); @@ -1581,7 +1578,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ $filter.unbind('click').click(function () { self.view.query.reset([{ - category: _("Custom Filter"), + category: _t("Custom Filter"), icon: 'M', field: { get_context: function () { return filter.context; }, @@ -1599,8 +1596,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }, save_current: function () { var self = this; - var $name = this.$element.find('input:first'); - var private_filter = !this.$element.find('input:last').prop('checked'); + var $name = this.$el.find('input:first'); + var private_filter = !this.$el.find('input:last').prop('checked'); var search = this.view.build_search_data(); this.rpc('/web/session/eval_domain_and_context', { @@ -1613,7 +1610,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } var filter = { name: $name.val(), - user_id: private_filter ? instance.connection.uid : false, + user_id: private_filter ? instance.session.uid : false, model_id: self.view.model, context: results.context, domain: results.domain @@ -1622,7 +1619,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ return self.model.call('create_or_replace', [filter]).then(function (id) { filter.id = id; self.append_filter(filter); - self.$element + self.$el .removeClass('oe_opened') .find('form')[0].reset(); }); @@ -1667,8 +1664,8 @@ instance.web.search.Filters = instance.web.search.Input.extend({ } return $.when( - this.render_column(col1, $('<div>').appendTo(this.$element)), - this.render_column(col2, $('<div>').appendTo(this.$element))); + this.render_column(col1, $('<div>').appendTo(this.$el)), + this.render_column(col2, $('<div>').appendTo(this.$el))); }, render_column: function (column, $el) { return $.when.apply(null, _(column).map(function (group) { @@ -1678,84 +1675,16 @@ instance.web.search.Filters = instance.web.search.Input.extend({ })); } }); -instance.web.search.AddToReporting = instance.web.Widget.extend({ - template: 'SearchView.addtoreporting', - _in_drawer: true, - start: function () { - var self = this; - this.$element - .on('click', 'h4', this.proxy('show_option')) - .on('submit', 'form', function (e) { - e.preventDefault(); - self.add_dashboard(); - }); - return this.load_data().then(this.proxy("render_data")); - }, - load_data:function(){ - if (!instance.webclient) { return $.Deferred().reject(); } - var dashboard_menu = instance.webclient.menu.data.data.children; - return new instance.web.Model('ir.model.data') - .query(['res_id']) - .filter([['name','=','menu_reporting_dashboard']]) - .first().pipe(function (result) { - var menu = _(dashboard_menu).chain() - .pluck('children') - .flatten(true) - .find(function (child) { return child.id === result.res_id; }) - .value(); - return menu ? menu.children : []; - }); - }, - render_data: function(dashboard_choices){ - var selection = instance.web.qweb.render( - "SearchView.addtoreporting.selection", { - selections: dashboard_choices}); - this.$("input").before(selection) - }, - add_dashboard:function(){ - var self = this; - var getParent = this.getParent(); - var view_parent = this.getParent().getParent(); - if (! view_parent.action || ! this.$element.find("select").val()) - return this.do_warn("Can't find dashboard action"); - var data = getParent.build_search_data(); - var context = new instance.web.CompoundContext(getParent.dataset.get_context() || []); - var domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []); - _.each(data.contexts, context.add, context); - _.each(data.domains, domain.add, domain); - this.rpc('/web/searchview/add_to_dashboard', { - menu_id: this.$element.find("select").val(), - action_id: view_parent.action.id, - context_to_save: context, - domain: domain, - view_mode: view_parent.active_view, - name: this.$element.find("input").val() - }, function(r) { - if (r === false) { - self.do_warn("Could not add filter to dashboard"); - } else { - self.$element.toggleClass('oe_opened'); - self.do_notify("Filter added to dashboard", ''); - } - }); - }, - show_option:function(){ - this.$element.toggleClass('oe_opened'); - if (! this.$element.hasClass('oe_opened')) - return; - this.$("input").val(this.getParent().fields_view.name || "" ); - } -}); instance.web.search.Advanced = instance.web.search.Input.extend({ template: 'SearchView.advanced', _in_drawer: true, start: function () { var self = this; - this.$element + this.$el .on('keypress keydown keyup', function (e) { e.stopPropagation(); }) .on('click', 'h4', function () { - self.$element.toggleClass('oe_opened'); + self.$el.toggleClass('oe_opened'); }).on('click', 'button.oe_add_condition', function () { self.append_proposition(); }).on('submit', 'form', function (e) { @@ -1774,7 +1703,7 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ }, append_proposition: function () { return (new instance.web.search.ExtendedSearchProposition(this, this.fields)) - .appendTo(this.$element.find('ul')); + .appendTo(this.$el.find('ul')); }, commit_search: function () { var self = this; @@ -1801,15 +1730,15 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ // add new empty proposition this.append_proposition(); // TODO: API on searchview - this.view.$element.removeClass('oe_searchview_open_drawer'); + this.view.$el.removeClass('oe_searchview_open_drawer'); } }); -instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ +instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ template: 'SearchView.extended_search.proposition', /** * @constructs instance.web.search.ExtendedSearchProposition - * @extends instance.web.OldWidget + * @extends instance.web.Widget * * @param parent * @param fields @@ -1825,16 +1754,16 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* }, start: function () { var _this = this; - this.$element.find(".searchview_extended_prop_field").change(function() { + this.$el.find(".searchview_extended_prop_field").change(function() { _this.changed(); }); - this.$element.find('.searchview_extended_delete_prop').click(function () { + this.$el.find('.searchview_extended_delete_prop').click(function () { _this.destroy(); }); this.changed(); }, changed: function() { - var nval = this.$element.find(".searchview_extended_prop_field").val(); + var nval = this.$el.find(".searchview_extended_prop_field").val(); if(this.attrs.selected == null || nval != this.attrs.selected.name) { this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); } @@ -1849,7 +1778,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* if(this.attrs.selected != null) { this.value.destroy(); this.value = null; - this.$element.find('.searchview_extended_prop_op').html(''); + this.$el.find('.searchview_extended_prop_op').html(''); } this.attrs.selected = field; if(field == null) { @@ -1865,9 +1794,9 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* _.each(this.value.operators, function(operator) { $('<option>', {value: operator.value}) .text(String(operator.text)) - .appendTo(self.$element.find('.searchview_extended_prop_op')); + .appendTo(self.$el.find('.searchview_extended_prop_op')); }); - var $value_loc = this.$element.find('.searchview_extended_prop_value').empty(); + var $value_loc = this.$el.find('.searchview_extended_prop_value').empty(); this.value.appendTo($value_loc); }, @@ -1875,7 +1804,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* if ( this.attrs.selected == null) return null; var field = this.attrs.selected; - var op = this.$element.find('.searchview_extended_prop_op')[0]; + var op = this.$el.find('.searchview_extended_prop_op')[0]; var operator = op.options[op.selectedIndex]; return { label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), { @@ -1919,7 +1848,7 @@ instance.web.search.ExtendedSearchProposition.Char = instance.web.search.Extende {value: "!=", text: _lt("is not equal to")} ], get_value: function() { - return this.$element.val(); + return this.$el.val(); } }); instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.ExtendedSearchProposition.Field.extend({ @@ -1943,7 +1872,7 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext start: function() { var ready = this._super(); this.datewidget = new (this.widget())(this); - this.datewidget.appendTo(this.$element); + this.datewidget.appendTo(this.$el); return ready; } }); @@ -1961,11 +1890,11 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte {value: "<=", text: _lt("less or equal than")} ], toString: function () { - return this.$element.val(); + return this.$el.val(); }, get_value: function() { try { - return instance.web.parse_value(this.$element.val(), {'widget': 'integer'}); + return instance.web.parse_value(this.$el.val(), {'widget': 'integer'}); } catch (e) { return ""; } @@ -1985,11 +1914,11 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend {value: "<=", text: _lt("less or equal than")} ], toString: function () { - return this.$element.val(); + return this.$el.val(); }, get_value: function() { try { - return instance.web.parse_value(this.$element.val(), {'widget': 'float'}); + return instance.web.parse_value(this.$el.val(), {'widget': 'float'}); } catch (e) { return ""; } @@ -2002,12 +1931,12 @@ instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.Ex {value: "!=", text: _lt("is not")} ], toString: function () { - var select = this.$element[0]; + var select = this.$el[0]; var option = select.options[select.selectedIndex]; return option.label || option.text; }, get_value: function() { - return this.$element.val(); + return this.$el.val(); } }); instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.ExtendedSearchProposition.Field.extend({ diff --git a/addons/web/static/src/js/test_support.js b/addons/web/static/src/js/test_support.js index d46366326b7..8e1317ba2b0 100644 --- a/addons/web/static/src/js/test_support.js +++ b/addons/web/static/src/js/test_support.js @@ -1,18 +1,17 @@ openerp.test_support = { - setup_connection: function (connection) { + setup_session: function (session) { var origin = location.protocol+"//"+location.host; - _.extend(connection, { + _.extend(session, { origin: origin, prefix: origin, server: origin, // keep chs happy //openerp.web.qweb.default_dict['_s'] = this.origin; - rpc_function: connection.rpc_json, + rpc_function: session.rpc_json, session_id: false, uid: false, username: false, user_context: {}, db: false, - openerp_entreprise: false, // this.module_list = openerp._modules.slice(); // this.module_loaded = {}; // _(this.module_list).each(function (mod) { @@ -22,7 +21,7 @@ openerp.test_support = { shortcuts: [], active_id: null }); - return connection.session_reload(); + return session.session_reload(); }, module: function (title, tested_core, nonliterals) { var conf = QUnit.config.openerp = {}; @@ -31,10 +30,10 @@ openerp.test_support = { QUnit.stop(); var oe = conf.openerp = window.openerp.init(); window.openerp.web[tested_core](oe); - var done = openerp.test_support.setup_connection(oe.connection); + var done = openerp.test_support.setup_session(oe.session); if (nonliterals) { done = done.pipe(function () { - return oe.connection.rpc('/tests/add_nonliterals', { + return oe.session.rpc('/tests/add_nonliterals', { domains: nonliterals.domains || [], contexts: nonliterals.contexts || [] }).then(function (r) { diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c9914c29dbc..4d69e07489a 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -13,23 +13,52 @@ instance.web.form = {}; * Properties: * - display_invalid_fields : if true, all fields where is_valid() return true should * be displayed as invalid. + * - actual_mode : the current mode of the field manager. Can be "view", "edit" or "create". * Events: * - view_content_has_changed : when the values of the fields have changed. When * this event is triggered all fields should reprocess their modifiers. + * - field_changed:<field_name> : when the value of a field change, an event is triggered + * named "field_changed:<field_name>" with <field_name> replaced by the name of the field. + * This event is not related to the on_change mechanism of OpenERP and is always called + * when the value of a field is setted or changed. This event is only triggered when the + * value of the field is syntactically valid, but it can be triggered when the value + * is sematically invalid (ie, when a required field is false). It is possible that an event + * about a precise field is never triggered even if that field exists in the view, in that + * case the value of the field is assumed to be false. */ instance.web.form.FieldManagerMixin = { /** * Must return the asked field as in fields_get. */ - get_field: function(field_name) {}, + get_field_desc: function(field_name) {}, /** - * Called by the field when the translate button is clicked. + * Returns the current value of a field present in the view. See the get_value() method + * method in FieldInterface for further information. */ - open_translate_dialog: function(field) {}, + get_field_value: function(field_name) {}, /** - * Returns true when the view is in create mode. - */ - is_create_mode: function() {}, + Gives new values for the fields contained in the view. The new values could not be setted + right after the call to this method. Setting new values can trigger on_changes. + + @param (dict) values A dictonnary with key = field name and value = new value. + @return (Deferred) Is resolved after all the values are setted. + */ + set_values: function(values) {}, + /** + Computes an OpenERP domain. + + @param (list) expression An OpenERP domain. + @return (boolean) The computed value of the domain. + */ + compute_domain: function(expression) {}, + /** + Builds an evaluation context for the resolution of the fields' contexts. Please note + the field are only supposed to use this context to evualuate their own, they should not + extend it. + + @return (CompoundContext) An OpenERP context. + */ + build_eval_context: function() {}, }; instance.web.views.add('form', 'instance.web.FormView'); @@ -74,6 +103,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.default_focus_button = null; this.fields_registry = instance.web.form.widgets; this.tags_registry = instance.web.form.tags; + this.widgets_registry = instance.web.form.custom_widgets; this.has_been_loaded = $.Deferred(); this.translatable_fields = []; _.defaults(this.options, { @@ -96,14 +126,19 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM self.on("change:actual_mode", self, self.init_pager); self.init_pager(); }); + instance.web.bus.on('clear_uncommitted_changes', this, function(e) { + if (!this.can_be_discarded()) { + e.preventDefault(); + } + }); }, destroy: function() { _.each(this.get_widgets(), function(w) { w.off('focused blurred'); w.destroy(); }); - if (this.$element) { - this.$element.off('.formBlur'); + if (this.$el) { + this.$el.off('.formBlur'); } this._super(); }, @@ -120,13 +155,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.rendering_engine.set_fields_registry(this.fields_registry); this.rendering_engine.set_tags_registry(this.tags_registry); + this.rendering_engine.set_widgets_registry(this.widgets_registry); if (!this.extract_qweb_template(data)) { this.rendering_engine.set_fields_view(data); - var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); this.rendering_engine.render_to($dest); } - this.$element.on('mousedown.formBlur', function () { + this.$el.on('mousedown.formBlur', function () { self.__clicked_inside = true; }); @@ -134,36 +170,47 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM if (this.options.$buttons) { this.$buttons.appendTo(this.options.$buttons); } else { - this.$element.find('.oe_form_buttons').replaceWith(this.$buttons); + this.$el.find('.oe_form_buttons').replaceWith(this.$buttons); } this.$buttons.on('click','.oe_form_button_create',this.on_button_create); this.$buttons.on('click','.oe_form_button_edit',this.on_button_edit); this.$buttons.on('click','.oe_form_button_save',this.on_button_save); this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel); - this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar'); + this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar'); if (!this.sidebar && this.options.$sidebar) { this.sidebar = new instance.web.Sidebar(this); this.sidebar.appendTo(this.$sidebar); - if(this.fields_view.toolbar) { + if (this.fields_view.toolbar) { this.sidebar.add_toolbar(this.fields_view.toolbar); } - this.sidebar.add_items('other', [ - { label: _t('Delete'), callback: self.on_button_delete }, - { label: _t('Duplicate'), callback: self.on_button_duplicate }, + this.sidebar.add_items('other', _.compact([ + self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete }, + self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate }, { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } } - ]); + ])); } + this.has_been_loaded.resolve(); + // Add bounce effect on button 'Edit' when click on readonly page view. - this.$element.find(".oe_form_field, .oe_form_group_cell").on('click', function (e) { + this.$el.find(".oe_form_group_row,.oe_form_field,label").on('click', function (e) { if(self.get("actual_mode") == "view") { var $button = self.options.$buttons.find(".oe_form_button_edit"); - $button.wrap('<div>').css('margin-right','4px').addClass('oe_left oe_bounce'); + $button.effect('bounce', {distance: 18, times: 5}, 150); + e.stopPropagation(); + instance.web.bus.trigger('click', e); } }); + //bounce effect on red button when click on statusbar. + this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) { + if((self.get("actual_mode") == "view")) { + var $button = self.$el.find(".oe_highlight:not(.oe_form_invisible)").css({'float':'left','clear':'none'}); + $button.effect('bounce', {distance:18, times: 5}, 150); + e.stopPropagation(); + } + }); - this.has_been_loaded.resolve(); return $.when(); }, extract_qweb_template: function(fvg) { @@ -214,7 +261,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.default_focus_field = null; this.default_focus_button = null; this.translatable_fields = []; - this.$element.find('.oe_form_container').empty(); + this.$el.find('.oe_form_container').empty(); }, widgetFocused: function() { @@ -254,7 +301,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM /** * * @param {Object} [options] - * @param {Boolean} [editable=false] whether the form should be switched to edition mode. A value of ``false`` will keep the current mode. + * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view". * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record * @return {$.Deferred} */ @@ -262,19 +309,16 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var self = this; options = options || {}; if (this.sidebar) { - this.sidebar.$element.show(); + this.sidebar.$el.show(); } if (this.$buttons) { this.$buttons.show(); } - if (this.$pager) { - this.$pager.show(); - } - this.$element.show().css({ + this.$el.show().css({ opacity: '0', filter: 'alpha(opacity = 0)' }); - this.$element.add(this.$buttons).removeClass('oe_form_dirty'); + this.$el.add(this.$buttons).removeClass('oe_form_dirty'); var shown = this.has_been_loaded; if (options.reload !== false) { @@ -291,10 +335,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }); } return shown.pipe(function() { - if (options.editable) { - self.to_edit_mode(); - } - self.$element.css({ + self._actualize_mode(options.mode || self.options.initial_mode); + self.$el.css({ opacity: '1', filter: 'alpha(opacity = 100)' }); @@ -302,7 +344,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }, do_hide: function () { if (this.sidebar) { - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } if (this.$buttons) { this.$buttons.hide(); @@ -321,18 +363,20 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } this.datarecord = record; this._actualize_mode(); - this.set({ 'title' : record.id ? record.display_name : "New record" }); + this.set({ 'title' : record.id ? record.display_name : "New" }); if (this.qweb) { this.kill_current_form(); this.rendering_engine.set_fields_view(this.get_fvg_from_qweb(record)); - var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); this.rendering_engine.render_to($dest); } _(this.fields).each(function (field, f) { field._dirty_flag = false; + field._inhibit_on_change_flag = true; var result = field.set_value(self.datarecord[f] || false); + field._inhibit_on_change_flag = false; set_values.push(result); }); return $.when.apply(null, set_values).pipe(function() { @@ -355,8 +399,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } if (record.id) { self.do_push_state({id:record.id}); + } else { + self.do_push_state({}); } - self.$element.add(self.$buttons).removeClass('oe_form_dirty'); + self.$el.add(self.$buttons).removeClass('oe_form_dirty'); self.autofocus(); }); }, @@ -378,7 +424,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.trigger("view_content_has_changed"); }, do_notify_change: function() { - this.$element.add(this.$buttons).addClass('oe_form_dirty'); + this.$el.add(this.$buttons).addClass('oe_form_dirty'); }, on_pager_action: function(action) { if (this.can_be_discarded()) { @@ -405,11 +451,11 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.$pager.remove(); if (this.get("actual_mode") === "create") return; - this.$pager = $(QWeb.render("FormView.pager", {'widget':self})); + this.$pager = $(QWeb.render("FormView.pager", {'widget':self})).hide(); if (this.options.$pager) { this.$pager.appendTo(this.options.$pager); } else { - this.$element.find('.oe_form_pager').replaceWith(this.$pager); + this.$el.find('.oe_form_pager').replaceWith(this.$pager); } this.$pager.on('click','a[data-pager-action]',function() { var action = $(this).data('pager-action'); @@ -418,10 +464,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.do_update_pager(); }, do_update_pager: function(hide_index) { - var index = hide_index ? '-' : this.dataset.index + 1; - this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end() - .find('span.oe_pager_index').html(index).end() - .find('span.oe_pager_count').html(this.dataset.ids.length); + this.$pager.toggle(this.dataset.ids.length > 1); + if (hide_index) { + $(".oe_form_pager_state", this.$pager).html(""); + } else { + $(".oe_form_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); + } }, parse_on_change: function (on_change, widget) { var self = this; @@ -570,22 +618,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM on_processed_onchange: function(result, processed) { try { if (result.value) { - for (var f in result.value) { - if (!result.value.hasOwnProperty(f)) { continue; } - var field = this.fields[f]; - // If field is not defined in the view, just ignore it - if (field) { - var value_ = result.value[f]; - if (field.get_value() != value_) { - field.set_value(value_); - field._dirty_flag = true; - if (!_.contains(processed, field.name)) { - this.do_onchange(field, processed); - } - } - } - } - this.on_form_changed(); + this._internal_set_values(result.value, processed); } if (!_.isEmpty(result.warning)) { instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), { @@ -616,6 +649,33 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM return $.Deferred().reject(); } }, + _internal_set_values: function(values, exclude) { + exclude = exclude || []; + for (var f in values) { + if (!values.hasOwnProperty(f)) { continue; } + var field = this.fields[f]; + // If field is not defined in the view, just ignore it + if (field) { + var value_ = values[f]; + if (field.get_value() != value_) { + field._inhibit_on_change_flag = true; + field.set_value(value_); + field._inhibit_on_change_flag = false; + field._dirty_flag = true; + if (!_.contains(exclude, field.name)) { + this.do_onchange(field, exclude); + } + } + } + } + this.on_form_changed(); + }, + set_values: function(values) { + var self = this; + return this.on_change_mutex.exec(function() { + self._internal_set_values(values); + }); + }, /** * Ask the view to switch to view mode if possible. The view may not do it * if the current record is not yet saved. It will then stay in create mode. @@ -631,32 +691,36 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this._actualize_mode("edit"); }, /** - * Reactualize actual_mode. + * Ask the view to switch to a precise mode if possible. The view is free to + * not respect this command if the state of the dataset is not compatible with + * the new mode. For example, it is not possible to switch to edit mode if + * the current record is not yet saved in database. + * + * @param {string} [new_mode] Can be "edit", "view", "create" or undefined. If + * undefined the view will test the actual mode to check if it is still consistent + * with the dataset state. */ _actualize_mode: function(switch_to) { var mode = switch_to || this.get("actual_mode"); - if (! this.datarecord.id) + if (! this.datarecord.id) { mode = "create"; + } else if (mode === "create") { + mode = "edit"; + } this.set({actual_mode: mode}); }, check_actual_mode: function(source, options) { var self = this; if(this.get("actual_mode") === "view") { - self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly'); + self.$el.removeClass('oe_form_editable').addClass('oe_form_readonly'); self.$buttons.find('.oe_form_buttons_edit').hide(); self.$buttons.find('.oe_form_buttons_view').show(); self.$sidebar.show(); - _.each(this.fields,function(field){ - field.set({"force_readonly": true}); - }); } else { - self.$element.removeClass('oe_form_readonly').addClass('oe_form_editable'); + self.$el.removeClass('oe_form_readonly').addClass('oe_form_editable'); self.$buttons.find('.oe_form_buttons_edit').show(); self.$buttons.find('.oe_form_buttons_view').hide(); self.$sidebar.hide(); - _.each(this.fields,function(field){ - field.set({"force_readonly": false}); - }); this.autofocus(); } }, @@ -684,8 +748,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }, on_button_cancel: function(event) { if (this.can_be_discarded()) { - this.to_view_mode(); - this.on_record_loaded(this.datarecord); + if (this.get('actual_mode') === 'create') { + this.trigger('history_back'); + } else { + this.to_view_mode(); + this.on_record_loaded(this.datarecord); + } } return false; }, @@ -737,17 +805,24 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM return def.promise(); }, can_be_discarded: function() { - return !this.$element.is('.oe_form_dirty') || confirm(_t("Warning, the record has been modified, your changes will be discarded.")); + if (this.$el.is('.oe_form_dirty')) { + if (!confirm(_t("Warning, the record has been modified, your changes will be discarded.\n\nAre you sure you want to leave this page ?"))) { + return false; + } + this.$el.removeClass('oe_form_dirty'); + } + return true; }, /** * Triggers saving the form's record. Chooses between creating a new * record or saving an existing one depending on whether the record * already has an id property. * - * @param {Function} [success] callback on save success - * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new record, should that record be inserted at the start of the dataset (by default, records are added at the end) + * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new + * record, should that record be inserted at the start of the dataset (by + * default, records are added at the end) */ - do_save: function(success, prepend_on_create) { + do_save: function(prepend_on_create) { var self = this; return this.mutating_mutex.exec(function() { return self.is_initialized.pipe(function() { try { @@ -782,21 +857,21 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM self.set({'display_invalid_fields': false}); var save_deferral; if (!self.datarecord.id) { - //console.log("FormView(", self, ") : About to create", values); + // Creation save save_deferral = self.dataset.create(values).pipe(function(r) { - return self.on_created(r, undefined, prepend_on_create); + return self.on_created(r, prepend_on_create); }, null); } else if (_.isEmpty(values) && ! self.force_dirty) { - //console.log("FormView(", self, ") : Nothing to save"); + // Not dirty, noop save save_deferral = $.Deferred().resolve({}).promise(); } else { self.force_dirty = false; - //console.log("FormView(", self, ") : About to save", values); + // Write save save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { return self.on_saved(r); }, null); } - return save_deferral.then(success); + return save_deferral; } } catch (e) { console.error(e); @@ -815,14 +890,19 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM warnings.push('</ul>'); this.do_warn("The following fields are invalid :", warnings.join('')); }, - on_saved: function(r, success) { - if (!r.result) { + /** + * Reload the form after saving + * + * @param {Object} r result of the write function. + */ + on_saved: function(r) { + if (!r) { // should not happen in the server, but may happen for internal purpose return $.Deferred().reject(); } else { return $.when(this.reload()).pipe(function () { - return r; }) - .then(success); + return r; + }); } }, /** @@ -835,15 +915,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM * * Updates the pager and sidebar displays * * @param {Object} r - * @param {Function} success callback to execute after having updated the dataset - * @param {Boolean} [prepend_on_create=false] adds the newly created record at the beginning of the dataset instead of the end + * @param {Boolean} [prepend_on_create=false] adds the newly created record + * at the beginning of the dataset instead of the end */ - on_created: function(r, success, prepend_on_create) { - if (!r.result) { + on_created: function(r, prepend_on_create) { + if (!r) { // should not happen in the server, but may happen for internal purpose return $.Deferred().reject(); } else { - this.datarecord.id = r.result; + this.datarecord.id = r; if (!prepend_on_create) { this.dataset.alter_ids(this.dataset.ids.concat([this.datarecord.id])); this.dataset.index = this.dataset.ids.length - 1; @@ -857,8 +937,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } //openerp.log("The record has been created with id #" + this.datarecord.id); return $.when(this.reload()).pipe(function () { - return _.extend(r, {created: true}); }) - .then(success); + return _.extend(r, {created: true}); + }); } }, on_action: function (action) { @@ -911,6 +991,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM return self.dataset.parent_view.recursive_save(); }); }, + recursive_reload: function() { + var self = this; + var pre = $.when(); + if (self.dataset.parent_view) + pre = self.dataset.parent_view.recursive_reload(); + return pre.pipe(function() { + return self.reload(); + }); + }, is_dirty: function() { return _.any(this.fields, function (value_) { return value_._dirty_flag; @@ -941,19 +1030,17 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM || field.get('invisible') || field.get("readonly") || field.field.type === 'one2many' - || field.field.type === 'many2many') { + || field.field.type === 'many2many' + || field.field.type === 'binary') { return false; } var displayed = value; - switch (field.field.type) { - case 'selection': + if (field instanceof instance.web.form.FieldSelection) { displayed = _(field.values).find(function (option) { return option[0] === value; })[1]; - break; - case 'many2one': + } else if (field instanceof instance.web.form.FieldMany2One) { displayed = field.get_displayed(); - break; } return { @@ -981,14 +1068,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM buttons: [ {text: _t("Close"), click: function () { d.close(); }}, {text: _t("Save default"), click: function () { - var $defaults = d.$element.find('#formview_default_fields'); + var $defaults = d.$el.find('#formview_default_fields'); var field_to_set = $defaults.val(); if (!field_to_set) { $defaults.parent().addClass('oe_form_invalid'); return; } - var condition = d.$element.find('#formview_default_conditions').val(), - all_users = d.$element.find('#formview_default_all').is(':checked'); + var condition = d.$el.find('#formview_default_conditions').val(), + all_users = d.$el.find('#formview_default_all').is(':checked'); new instance.web.DataSet(self, 'ir.values').call( 'set_default', [ self.dataset.model, @@ -1013,10 +1100,16 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM field.on('focused', null, this.proxy('widgetFocused')) .on('blurred', null, this.proxy('widgetBlurred')); - if (this.get_field(name).translate) { + if (this.get_field_desc(name).translate) { this.translatable_fields.push(field); } field.on('changed_value', this, function() { + if (field.is_syntax_valid()) { + this.trigger('field_changed:' + name); + } + if (field._inhibit_on_change_flag) { + return; + } field._dirty_flag = true; if (field.is_syntax_valid()) { this.do_onchange(field); @@ -1025,14 +1118,33 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } }); }, - get_field: function(field_name) { + get_field_desc: function(field_name) { return this.fields_view.fields[field_name]; }, - is_create_mode: function() { - return this.get("actual_mode") === "create"; + get_field_value: function(field_name) { + return this.fields[field_name].get_value(); + }, + compute_domain: function(expression) { + return instance.web.form.compute_domain(expression, this.fields); }, - open_translate_dialog: function(field) { - return this._super(field); + _build_view_fields_values: function(blacklist) { + var a_dataset = this.dataset; + var fields_values = this.get_fields_values(blacklist); + var active_id = a_dataset.ids[a_dataset.index]; + _.extend(fields_values, { + active_id: active_id || false, + active_ids: active_id ? [active_id] : [], + active_model: a_dataset.model, + parent: {} + }); + if (a_dataset.parent_view) { + fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]); + } + return fields_values; + }, + build_eval_context: function(blacklist) { + var a_dataset = this.dataset; + return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); }, }); @@ -1042,7 +1154,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM instance.web.form.FormRenderingEngineInterface = instance.web.Class.extend({ set_fields_view: function(fields_view) {}, set_fields_registry: function(fields_registry) {}, - render_to: function($element) {}, + render_to: function($el) {}, }); /** @@ -1067,6 +1179,9 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt set_fields_registry: function(fields_registry) { this.fields_registry = fields_registry; }, + set_widgets_registry: function(widgets_registry) { + this.widgets_registry = widgets_registry; + }, // Backward compatibility tools, current default version: v6.1 process_version: function() { if (this.version < 7.0) { @@ -1091,6 +1206,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt this.fields_to_init = []; this.tags_to_init = []; + this.widgets_to_init = []; this.labels = {}; this.process(this.$form); @@ -1120,6 +1236,12 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); w.replace($elem); }); + _.each(this.widgets_to_init, function($elem) { + var widget_type = $elem.attr("type"); + var obj = self.widgets_registry.get_object(widget_type); + var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); + w.replace($elem); + }); // TODO: return a deferred }, render_element: function(template /* dictionaries */) { @@ -1162,6 +1284,10 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt return $tag; } }, + process_widget: function($widget) { + this.widgets_to_init.push($widget); + return $widget; + }, process_sheet: function($sheet) { var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes()); this.handle_common_properties($new_sheet, $sheet); @@ -1377,6 +1503,10 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt if (! page.__ic) return; page.__ic.on("change:effective_invisible", null, function() { + if (!page.__ic.get('effective_invisible')) { + $new_notebook.tabs('select', i); + return; + } var current = $new_notebook.tabs("option", "selected"); if (! pages[current].__ic || ! pages[current].__ic.get("effective_invisible")) return; @@ -1435,6 +1565,48 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt }, }); +/** + Welcome. + + If you read this documentation, it probably means that you were asked to use a form view widget outside of + a form view. Before going further, you must understand that those fields were never really created for + that usage. Don't think that this class will hold the answer to all your problems, at best it will allow + you to hack the system with more style. +*/ +instance.web.form.DefaultFieldManager = instance.web.Widget.extend({ + init: function(parent, eval_context) { + this._super(parent); + this.field_descs = {}; + this.eval_context = eval_context || {}; + }, + get_field_desc: function(field_name) { + if (this.field_descs[field_name] === undefined) { + this.field_descs[field_name] = { + string: field_name, + }; + } + return this.field_descs[field_name]; + }, + extend_field_desc: function(fields) { + var self = this; + _.each(fields, function(v, k) { + _.extend(self.get_field_desc(k), v); + }); + }, + get_field_value: function(field_name) { + return false; + }, + set_values: function(values) { + // nothing + }, + compute_domain: function(expression) { + return instance.web.form.compute_domain(expression, {}); + }, + build_eval_context: function() { + return new instance.web.CompoundContext(this.eval_context); + }, +}); + instance.web.form.FormDialog = instance.web.Dialog.extend({ init: function(parent, options, view_id, dataset) { this._super(parent, options); @@ -1447,7 +1619,7 @@ instance.web.form.FormDialog = instance.web.Dialog.extend({ this.form = new instance.web.FormView(this, this.dataset, this.view_id, { pager: false }); - this.form.appendTo(this.$element); + this.form.appendTo(this.$el); this.form.on_created.add_last(this.on_form_dialog_saved); this.form.on_saved.add_last(this.on_form_dialog_saved); return this; @@ -1539,7 +1711,7 @@ instance.web.form.compute_domain = function(expr, fields) { /** * Must be applied over an class already possessing the PropertiesMixin. * - * Apply the result of the "invisible" domain to this.$element. + * Apply the result of the "invisible" domain to this.$el. */ instance.web.form.InvisibilityChangerMixin = { init: function(field_manager, invisible_domain) { @@ -1548,9 +1720,7 @@ instance.web.form.InvisibilityChangerMixin = { this._ic_invisible_modifier = invisible_domain; this._ic_field_manager.on("view_content_has_changed", this, function() { var result = self._ic_invisible_modifier === undefined ? false : - instance.web.form.compute_domain( - self._ic_invisible_modifier, - self._ic_field_manager.fields); + self._ic_field_manager.compute_domain(self._ic_invisible_modifier); self.set({"invisible": result}); }); this.set({invisible: this._ic_invisible_modifier === true, force_invisible: false}); @@ -1570,40 +1740,61 @@ instance.web.form.InvisibilityChangerMixin = { this._check_visibility(); }, _check_visibility: function() { - this.$element.toggleClass('oe_form_invisible', this.get("effective_invisible")); + this.$el.toggleClass('oe_form_invisible', this.get("effective_invisible")); }, }; instance.web.form.InvisibilityChanger = instance.web.Class.extend(instance.web.PropertiesMixin, instance.web.form.InvisibilityChangerMixin, { - init: function(parent, field_manager, invisible_domain, $element) { + init: function(parent, field_manager, invisible_domain, $el) { this.setParent(parent); instance.web.PropertiesMixin.init.call(this); instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain); - this.$element = $element; + this.$el = $el; this.start(); }, }); +/** + Base class for all fields, custom widgets and buttons to be displayed in the form view. + + Properties: + - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending + the values of the "readonly" property and the "mode" property on the field manager. +*/ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.InvisibilityChangerMixin, { /** * @constructs instance.web.form.FormWidget * @extends instance.web.Widget * - * @param view + * @param field_manager * @param node */ - init: function(view, node) { - this._super(view); - this.view = view; + init: function(field_manager, node) { + this._super(field_manager); + this.field_manager = field_manager; + if (this.field_manager instanceof instance.web.FormView) + this.view = this.field_manager; this.node = node; this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}'); - instance.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible); + instance.web.form.InvisibilityChangerMixin.init.call(this, this.field_manager, this.modifiers.invisible); + + this.field_manager.on("view_content_has_changed", this, this.process_modifiers); - this.view.on("view_content_has_changed", this, this.process_modifiers); + this.set({required: this.modifiers['required'] === true}); + // some events to make the property "effective_readonly" sync automatically with "readonly" and + // "mode" on field_manager + var self = this; + this.set({"readonly": this.modifiers['readonly'] === true}); + var test_effective_readonly = function() { + self.set({"effective_readonly": self.get("readonly") || self.field_manager.get("actual_mode") === "view"}); + }; + this.on("change:readonly", this, test_effective_readonly); + this.field_manager.on("change:actual_mode", this, test_effective_readonly); + test_effective_readonly.call(this); }, renderElement: function() { this._super(); - this.$element.addClass(this.node.attrs["class"] || ""); + this.$el.addClass(this.node.attrs["class"] || ""); }, destroy: function() { $.fn.tipsy.clear(); @@ -1624,12 +1815,11 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi }); }, process_modifiers: function() { - var compute_domain = instance.web.form.compute_domain; var to_set = {}; for (var a in this.modifiers) { if (!this.modifiers.hasOwnProperty(a)) { continue; } if (!_.include(["invisible"], a)) { - var val = compute_domain(this.modifiers[a], this.view.fields); + var val = this.field_manager.compute_domain(this.modifiers[a]); to_set[a] = val; } } @@ -1637,7 +1827,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi }, do_attach_tooltip: function(widget, trigger, options) { widget = widget || this; - trigger = trigger || this.$element; + trigger = trigger || this.$el; options = _.extend({ delayIn: 500, delayOut: 0, @@ -1648,7 +1838,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi template = 'WidgetLabel.tooltip'; } return QWeb.render(template, { - debug: instance.connection.debug, + debug: instance.session.debug, widget: widget })}, gravity: $.fn.tipsy.autoBounds(50, 'nw'), @@ -1658,25 +1848,6 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi }, options || {}); $(trigger).tipsy(options); }, - _build_view_fields_values: function(blacklist) { - var a_dataset = this.view.dataset; - var fields_values = this.view.get_fields_values(blacklist); - var active_id = a_dataset.ids[a_dataset.index]; - _.extend(fields_values, { - active_id: active_id || false, - active_ids: active_id ? [active_id] : [], - active_model: a_dataset.model, - parent: {} - }); - if (a_dataset.parent_view) { - fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]); - } - return fields_values; - }, - _build_eval_context: function(blacklist) { - var a_dataset = this.view.dataset; - return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); - }, /** * Builds a new context usable for operations related to fields by merging * the fields'context with the action's context. @@ -1689,7 +1860,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi } if (v_context.__ref || true) { //TODO: remove true - var fields_values = this._build_eval_context(blacklist); + var fields_values = this.field_manager.build_eval_context(blacklist); v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values); } return v_context; @@ -1700,7 +1871,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi // if there is a domain on the node, overrides the model's domain var final_domain = n_domain !== null ? n_domain : f_domain; if (!(final_domain instanceof Array) || true) { //TODO: remove true - var fields_values = this._build_eval_context(); + var fields_values = this.field_manager.build_eval_context(); final_domain = new instance.web.CompoundDomain(final_domain).set_eval_context(fields_values); } return final_domain; @@ -1709,8 +1880,8 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({ template: 'WidgetButton', - init: function(view, node) { - this._super(view, node); + init: function(field_manager, node) { + this._super(field_manager, node); this.force_disabled = false; this.string = (this.node.attrs.string || '').replace(/_/g, ''); if (JSON.parse(this.node.attrs.default_focus || "0")) { @@ -1724,11 +1895,11 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({ }, start: function() { this._super.apply(this, arguments); - this.$element.click(this.on_click); - if (this.node.attrs.help || instance.connection.debug) { + this.$el.click(this.on_click); + if (this.node.attrs.help || instance.session.debug) { this.do_attach_tooltip(); } - this.setupFocus(this.$element); + this.setupFocus(this.$el); }, on_click: function() { var self = this; @@ -1782,25 +1953,22 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({ return this.view.do_execute_action( _.extend({}, this.node.attrs, {context: context}), this.view.dataset, this.view.datarecord.id, function () { - self.view.reload(); + self.view.recursive_reload(); }); }, check_disable: function() { var disabled = (this.force_disabled || !this.view.is_interactible_record()); - this.$element.prop('disabled', disabled); - this.$element.css('color', disabled ? 'grey' : ''); + this.$el.prop('disabled', disabled); + this.$el.css('color', disabled ? 'grey' : ''); } }); /** * Interface to be implemented by fields. * - * Properties: - * - readonly: boolean. If set to true the field should appear in readonly mode. - * - force_readonly: boolean, When it is true, the field should always appear - * in read only mode, no matter what the value of the "readonly" property can be. * Events: - * - changed_value: triggered to inform the view to check on_changes + * - changed_value: triggered when the value of the field has changed. This can be due + * to a user interaction or a call to set_value(). * */ instance.web.form.FieldInterface = { @@ -1864,14 +2032,16 @@ instance.web.form.FieldInterface = { * Must set the focus on the field. Return false if field is not focusable. */ focus: function() {}, + /** + * Called when the translate button is clicked. + */ + on_translate: function() {}, }; /** * Abstract class for classes implementing FieldInterface. * * Properties: - * - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending - * the values of the "readonly" property and the "force_readonly" property on the field manager. * - value: useful property to hold the value of the field. By default, set_value() and get_value() * set and retrieve the value property. Changing the value property also triggers automatically * a 'changed_value' event that inform the view to trigger on_changes. @@ -1888,42 +2058,28 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w init: function(field_manager, node) { var self = this this._super(field_manager, node); - this.field_manager = field_manager; this.name = this.node.attrs.name; - this.field = this.field_manager.get_field(this.name); + this.field = this.field_manager.get_field_desc(this.name); this.widget = this.node.attrs.widget; this.string = this.node.attrs.string || this.field.string || this.name; + this.options = JSON.parse(this.node.attrs.options || '{}'); this.set({'value': false}); - this.set({required: this.modifiers['required'] === true}); - // some events to make the property "effective_readonly" sync automatically with "readonly" and - // "force_readonly" - this.set({"readonly": this.modifiers['readonly'] === true}); - this.set({"force_readonly": false}); - var test_effective_readonly = function() { - self.set({"effective_readonly": self.get("readonly") || !!self.get("force_readonly")}); - }; - this.on("change:readonly", this, test_effective_readonly); - this.on("change:force_readonly", this, test_effective_readonly); - test_effective_readonly.call(this); this.on("change:value", this, function() { - if (! this._inhibit_on_change) - this.trigger('changed_value'); + this.trigger('changed_value'); this._check_css_flags(); }); }, renderElement: function() { var self = this; this._super(); - if (this.field.translate) { - this.$element.addClass('oe_form_field_translatable'); - this.$element.find('.oe_field_translate').click(_.bind(function() { - this.field_manager.open_translate_dialog(this); - }, this)); + if (this.field.translate && this.view) { + this.$el.addClass('oe_form_field_translatable'); + this.$el.find('.oe_field_translate').click(this.on_translate); } - this.$label = this.view.$element.find('label[for=' + this.id_for_label + ']'); - if (instance.connection.debug) { - this.do_attach_tooltip(this, this.$label[0] || this.$element); + this.$label = this.view ? this.view.$el.find('label[for=' + this.id_for_label + ']') : $(); + 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; @@ -1942,12 +2098,10 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w * Private. Do not use. */ _set_required: function() { - this.$element.toggleClass('oe_form_required', this.get("required")); + this.$el.toggleClass('oe_form_required', this.get("required")); }, set_value: function(value_) { - this._inhibit_on_change = true; this.set({'value': value_}); - this._inhibit_on_change = false; }, get_value: function() { return this.get('value'); @@ -1967,37 +2121,34 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w }, _check_css_flags: function() { if (this.field.translate) { - this.$element.find('.oe_field_translate').toggle(!this.field_manager.is_create_mode()); + this.$el.find('.oe_field_translate').toggle(this.field_manager.get('actual_mode') !== "create"); } if (!this.disable_utility_classes) { if (this.field_manager.get('display_invalid_fields')) { - this.$element.toggleClass('oe_form_invalid', !this.is_valid()); + this.$el.toggleClass('oe_form_invalid', !this.is_valid()); } } }, focus: function() { return false; }, - /** - * Utility method to get the widget options defined in the field xml description. - */ - get_definition_options: function() { - if (!this.definition_options) { - var str = this.node.attrs.options || '{}'; - this.definition_options = JSON.parse(str); - } - return this.definition_options; - }, set_input_id: function(id) { this.id_for_label = id; }, + on_translate: function() { + var self = this; + var trans = new instance.web.DataSet(this, 'ir.translation'); + return trans.call_button('translate_fields', [this.view.dataset.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).then(function(r) { + self.do_action(r); + }); + }, }); /** - * A mixin to apply on any field that has to completely re-render when its readonly state + * A mixin to apply on any FormWidget that has to completely re-render when its readonly state * switch. */ -instance.web.form.ReinitializeFieldMixin = { +instance.web.form.ReinitializeWidgetMixin = { /** * Default implementation of start(), use it or call explicitly initialize_field(). */ @@ -2010,10 +2161,8 @@ instance.web.form.ReinitializeFieldMixin = { this.destroy_content(); this.renderElement(); this.initialize_content(); - this.render_value(); }); this.initialize_content(); - this.render_value(); }, /** * Called to destroy anything that could have been created previously, called before a @@ -2024,11 +2173,25 @@ instance.web.form.ReinitializeFieldMixin = { * Called to initialize the content. */ initialize_content: function() {}, +}; + +/** + * A mixin to apply on any field that has to completely re-render when its readonly state + * switch. + */ +instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, { + initialize_field: function() { + instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this); + this.on("change:effective_readonly", this, function() { + this.render_value(); + }); + this.render_value(); + }, /** * Called to render the value. Should also be explicitly called at the end of a set_value(). */ render_value: function() {}, -}; +}); instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldChar', @@ -2039,7 +2202,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we }, initialize_content: function() { var self = this; - var $input = this.$element.find('input'); + var $input = this.$el.find('input'); $input.change(function() { self.set({'value': instance.web.parse_value($input.val(), self)}); }); @@ -2052,18 +2215,18 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we render_value: function() { var show_value = instance.web.format_value(this.get('value'), this, ''); if (!this.get("effective_readonly")) { - this.$element.find('input').val(show_value); + this.$el.find('input').val(show_value); } else { if (this.password) { show_value = new Array(show_value.length + 1).join('*'); } - this.$element.text(show_value); + this.$el.text(show_value); } }, is_syntax_valid: function() { if (!this.get("effective_readonly")) { try { - var value_ = instance.web.parse_value(this.$element.find('input').val(), this, ''); + var value_ = instance.web.parse_value(this.$el.find('input').val(), this, ''); return true; } catch(e) { return false; @@ -2075,7 +2238,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we return this.get('value') === '' || this._super(); }, focus: function() { - this.$element.find('input:first').focus(); + this.$('input:first').focus(); } }); @@ -2087,7 +2250,7 @@ instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({ template: 'FieldEmail', initialize_content: function() { this._super(); - var $button = this.$element.find('button'); + var $button = this.$el.find('button'); $button.click(this.on_button_clicked); this.setupFocus($button); }, @@ -2095,7 +2258,7 @@ instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({ if (!this.get("effective_readonly")) { this._super(); } else { - this.$element.find('a') + this.$el.find('a') .attr('href', 'mailto:' + this.get('value')) .text(this.get('value') || ''); } @@ -2113,7 +2276,7 @@ instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({ template: 'FieldUrl', initialize_content: function() { this._super(); - var $button = this.$element.find('button'); + var $button = this.$el.find('button'); $button.click(this.on_button_clicked); this.setupFocus($button); }, @@ -2126,7 +2289,7 @@ instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({ if (!s) { tmp = "http://" + this.get('value'); } - this.$element.find('a').attr('href', tmp).text(this.get('value') ? tmp : ''); + this.$el.find('a').attr('href', tmp).text(this.get('value') ? tmp : ''); } }, on_button_clicked: function() { @@ -2159,10 +2322,13 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({ value_ = 0; } this._super.apply(this, [value_]); + }, + focus: function () { + this.$('input:first').select(); } }); -instance.web.DateTimeWidget = instance.web.OldWidget.extend({ +instance.web.DateTimeWidget = instance.web.Widget.extend({ template: "web.datepicker", jqueryui_object: 'datetimepicker', type_of_date: "datetime", @@ -2172,8 +2338,8 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ }, start: function() { var self = this; - this.$input = this.$element.find('input.oe_datepicker_master'); - this.$input_picker = this.$element.find('input.oe_datepicker_container'); + this.$input = this.$el.find('input.oe_datepicker_master'); + this.$input_picker = this.$el.find('input.oe_datepicker_container'); this.$input.change(this.on_change); this.picker({ onClose: this.on_picker_select, @@ -2181,9 +2347,10 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ changeMonth: true, changeYear: true, showWeek: true, - showButtonPanel: true + showButtonPanel: true, + firstDay: Date.CultureInfo.firstDayOfWeek }); - this.$element.find('img.oe_datepicker_trigger').click(function() { + this.$el.find('img.oe_datepicker_trigger').click(function() { if (self.get("effective_readonly") || self.picker('widget').is(':visible')) { self.$input.focus(); return; @@ -2220,7 +2387,7 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ set_readonly: function(readonly) { this.readonly = readonly; this.$input.prop('readonly', this.readonly); - this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); + this.$el.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); }, is_valid_: function() { var value_ = this.$input.val(); @@ -2270,7 +2437,7 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc this.datewidget.on_change.add_last(_.bind(function() { this.set({'value': this.datewidget.get_value()}); }, this)); - this.datewidget.appendTo(this.$element); + this.datewidget.appendTo(this.$el); this.setupFocus(this.datewidget.$input); } }, @@ -2282,7 +2449,7 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc if (!this.get("effective_readonly")) { this.datewidget.set_value(this.get('value')); } else { - this.$element.text(instance.web.format_value(this.get('value'), this, '')); + this.$el.text(instance.web.format_value(this.get('value'), this, '')); } }, is_syntax_valid: function() { @@ -2310,16 +2477,21 @@ instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldText', + init: function (field_manager, node) { + this._super(field_manager, node); + }, initialize_content: function() { - this.$textarea = this.$element.find('textarea'); + var self = this; + this.$textarea = this.$el.find('textarea'); + this.default_height = this.$textarea.css('height'); if (!this.get("effective_readonly")) { this.$textarea.change(_.bind(function() { - this.set({'value': instance.web.parse_value(this.$textarea.val(), this)}); + self.set({'value': instance.web.parse_value(self.$textarea.val(), self)}); }, this)); } else { this.$textarea.attr('disabled', 'disabled'); } - this.$element.keyup(function (e) { + this.$el.keyup(function (e) { if (e.which === $.ui.keyCode.ENTER) { e.stopPropagation(); } @@ -2327,15 +2499,17 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we this.setupFocus(this.$textarea); }, set_value: function(value_) { - this._super.apply(this, arguments); + this._super(value_); this.render_value(); + $(window).resize(); }, render_value: function() { var show_value = instance.web.format_value(this.get('value'), this, ''); - this.$textarea.val(show_value); - if (show_value && this.view.options.resize_textareas) { - this.do_resize(this.view.options.resize_textareas); + if (show_value === '') { + this.$textarea.css('height', parseInt(this.default_height)+"px"); } + this.$textarea.val(show_value); + this.$textarea.autosize(); }, is_syntax_valid: function() { if (!this.get("effective_readonly")) { @@ -2351,29 +2525,9 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we is_false: function() { return this.get('value') === '' || this._super(); }, - focus: function($element) { + focus: function($el) { this.$textarea.focus(); }, - do_resize: function(max_height) { - max_height = parseInt(max_height, 10); - var $input = this.$textarea, - $div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()), - new_height; - $div.text($input.val()); - _.each('font-family,font-size,white-space'.split(','), function(style) { - $div.css(style, $input.css(style)); - }); - $div.appendTo($('body')); - new_height = $div.height(); - if (new_height < 90) { - new_height = 90; - } - if (!isNaN(max_height) && new_height > max_height) { - new_height = max_height; - } - $div.remove(); - $input.height(new_height); - }, }); /** @@ -2383,44 +2537,50 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we * To find more information about CLEditor configutation: go to * http://premiumsoftware.net/cleditor/docs/GettingStarted.html */ -instance.web.form.FieldTextHtml = instance.web.form.FieldText.extend({ - - initialize_content: function() { - this.$textarea = this.$element.find('textarea'); - var width = ((this.node.attrs || {}).editor_width || 468); - var height = ((this.node.attrs || {}).editor_height || 100); - this.$textarea.cleditor({ - width: width, // width not including margins, borders or padding - height: height, // height not including margins, borders or padding - controls: // controls to add to the toolbar - "bold italic underline strikethrough | size " + - "| removeformat | bullets numbering | outdent " + - "indent | link unlink", - sizes: // sizes in the font size popup - "1,2,3,4,5,6,7", - bodyStyle: // style to assign to document body contained within the editor - "margin:4px; font:12px monospace; cursor:text; color:#1F1F1F" - }); - this.$cleditor = this.$textarea.cleditor()[0]; - // call super now, because cleditor resets the disable attr +instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { + template: 'FieldTextHtml', + init: function() { this._super.apply(this, arguments); - // propagate disabled property to cleditor - this.$cleditor.disable(this.$textarea.prop('disabled')); }, - + initialize_content: function() { + var self = this; + if (! this.get("effective_readonly")) { + self._updating_editor = false; + this.$textarea = this.$el.find('textarea'); + var width = ((this.node.attrs || {}).editor_width || 468); + var height = ((this.node.attrs || {}).editor_height || 250); + this.$textarea.cleditor({ + width: width, // width not including margins, borders or padding + height: height, // height not including margins, borders or padding + controls: // controls to add to the toolbar + "bold italic underline strikethrough " + + "| removeformat | bullets numbering | outdent " + + "indent | link unlink | source", + bodyStyle: // style to assign to document body contained within the editor + "margin:4px; font:12px monospace; cursor:text; color:#1F1F1F" + }); + this.$cleditor = this.$textarea.cleditor()[0]; + this.$cleditor.change(function() { + if (! self._updating_editor) { + self.$cleditor.updateTextArea(); + self.set({'value': self.$textarea.val()}); + } + }); + } + }, set_value: function(value_) { this._super.apply(this, arguments); - this._dirty_flag = true; + this.render_value(); }, - render_value: function() { - this._super.apply(this, arguments); - this.$cleditor.updateFrame(); - }, - - get_value: function() { - this.$cleditor.updateTextArea(); - return this.$textarea.val(); + if (! this.get("effective_readonly")) { + this.$textarea.val(this.get('value') || ''); + this._updating_editor = true; + this.$cleditor.updateFrame(); + this._updating_editor = false; + } else { + this.$el.html(this.get('value')); + } }, }); @@ -2429,9 +2589,9 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({ start: function() { var self = this; this._super.apply(this, arguments); - this.$checkbox = $("input", this.$element); + this.$checkbox = $("input", this.$el); this.setupFocus(this.$checkbox); - this.$element.click(_.bind(function() { + this.$el.click(_.bind(function() { this.set({'value': this.$checkbox.is(':checked')}); }, this)); var check_readonly = function() { @@ -2453,7 +2613,7 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ template: 'FieldProgressBar', start: function() { this._super.apply(this, arguments); - this.$element.progressbar({ + this.$el.progressbar({ value: this.get('value'), disabled: this.get("effective_readonly") }); @@ -2465,7 +2625,7 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ show_value = 0; } var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0'); - this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); + this.$el.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); } }); @@ -2496,9 +2656,9 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan // changing the selected value), takes the action as validating the // row var ischanging = false; - var $select = this.$element.find('select') + var $select = this.$el.find('select') .change(_.bind(function() { - this.set({'value': this.values[this.$element.find('select')[0].selectedIndex][0]}); + this.set({'value': this.values[this.$el.find('select')[0].selectedIndex][0]}); }, this)) .change(function () { ischanging = true; }) .click(function () { ischanging = false; }) @@ -2521,27 +2681,27 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan for (var i = 0, ii = this.values.length; i < ii; i++) { if (this.values[i][0] === this.get('value')) index = i; } - this.$element.find('select')[0].selectedIndex = index; + this.$el.find('select')[0].selectedIndex = index; } else { var self = this; var option = _(this.values) .detect(function (record) { return record[0] === self.get('value'); }); - this.$element.text(option ? option[1] : this.values[0][1]); + this.$el.text(option ? option[1] : this.values[0][1]); } }, is_syntax_valid: function() { if (this.get("effective_readonly")) { return true; } - var value_ = this.values[this.$element.find('select')[0].selectedIndex]; + var value_ = this.values[this.$el.find('select')[0].selectedIndex]; return !! value_; }, focus: function() { - this.$element.find('select:first').focus(); + this.$el.find('select:first').focus(); } }); -// jquery autocomplete tweak to allow html +// jquery autocomplete tweak to allow html and classnames (function() { var proto = $.ui.autocomplete.prototype, initSource = proto._initSource; @@ -2568,7 +2728,8 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan return $( "<li></li>" ) .data( "item.autocomplete", item ) .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) ) - .appendTo( ul ); + .appendTo( ul ) + .addClass(item.classname); } }); })(); @@ -2589,6 +2750,7 @@ instance.web.form.CompletionFieldMixin = { var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context()); var blacklist = this.get_search_blacklist(); + this.last_query = search_val; return this.orderer.add(dataset.name_search( search_val, new instance.web.CompoundDomain(self.build_domain(), [["id", "not in", blacklist]]), @@ -2608,25 +2770,36 @@ instance.web.form.CompletionFieldMixin = { // search more... if more results that max if (values.length > self.limit) { values = values.slice(0, self.limit); - values.push({label: _t("<em>Â Â Â Search More...</em>"), action: function() { - dataset.name_search(search_val, self.build_domain(), 'ilike' - , false, function(data) { - self._search_create_popup("search", data); - }); - }}); + values.push({ + label: _t("Search More..."), + action: function() { + dataset.name_search(search_val, self.build_domain(), 'ilike', false).then(function(data) { + self._search_create_popup("search", data); + }); + }, + classname: 'oe_m2o_dropdown_option' + }); } // quick create var raw_result = _(data.result).map(function(x) {return x[1];}); if (search_val.length > 0 && !_.include(raw_result, search_val)) { - values.push({label: _.str.sprintf(_t('<em>Â Â Â Create "<strong>%s</strong>"</em>'), - $('<span />').text(search_val).html()), action: function() { - self._quick_create(search_val); - }}); + values.push({ + label: _.str.sprintf(_t('Create "<strong>%s</strong>"'), + $('<span />').text(search_val).html()), + action: function() { + self._quick_create(search_val); + }, + classname: 'oe_m2o_dropdown_option' + }); } // create... - values.push({label: _t("<em>Â Â Â Create and Edit...</em>"), action: function() { - self._search_create_popup("form", undefined, {}); - }}); + values.push({ + label: _t("Create and Edit..."), + action: function() { + self._search_create_popup("form", undefined, self._create_context(search_val)); + }, + classname: 'oe_m2o_dropdown_option' + }); return values; }); @@ -2637,11 +2810,11 @@ instance.web.form.CompletionFieldMixin = { _quick_create: function(name) { var self = this; var slow_create = function () { - self._search_create_popup("form", undefined, {"default_name": name}); + self._search_create_popup("form", undefined, self._create_context(name)); }; - if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) { + if (self.options.quick_create === undefined || self.options.quick_create) { new instance.web.DataSet(this, this.field.relation, self.build_context()) - .name_create(name, function(data) { + .name_create(name).then(function(data) { self.add_id(data[0]); }).fail(function(error, event) { event.preventDefault(); @@ -2674,8 +2847,43 @@ instance.web.form.CompletionFieldMixin = { * To implement. */ add_id: function(id) {}, + _create_context: function(name) { + var tmp = {}; + var field = (this.options || {}).create_name_field; + if (field === undefined) + field = "name"; + if (field !== false && name && (this.options || {}).quick_create !== false) + tmp["default_" + field] = name; + return tmp; + }, }; +instance.web.form.M2ODialog = instance.web.Dialog.extend({ + template: "M2ODialog", + init: function(parent) { + this._super(parent, { + title: _.str.sprintf(_t("Add %s"), parent.string), + width: 312, + }); + }, + start: function() { + var self = this; + this.$buttons.html(QWeb.render("M2ODialog.buttons")); + this.$("input").val(this.getParent().last_query); + this.$buttons.find(".oe_form_m2o_qc_button").click(function(){ + self.getParent()._quick_create(self.$("input").val()); + self.destroy(); + }); + this.$buttons.find(".oe_form_m2o_sc_button").click(function(){ + self.getParent()._search_create_popup("form", undefined, self.getParent()._create_context(self.$("input").val())); + self.destroy(); + }); + this.$buttons.find(".oe_form_m2o_cancel_button").click(function(){ + self.destroy(); + }); + }, +}); + instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, { template: "FieldMany2One", init: function(field_manager, node) { @@ -2695,33 +2903,46 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc this.floating = false; this.render_value(); }); + instance.web.bus.on('click', this, function() { + if (!this.get("effective_readonly") && this.$input && this.$input.autocomplete('widget').is(':visible')) { + this.$input.autocomplete("close"); + } + }); }, initialize_content: function() { if (!this.get("effective_readonly")) this.render_editable(); this.render_value(); }, + init_error_displayer: function() { + // nothing + }, + hide_error_displayer: function() { + // doesn't work + }, + show_error_displayer: function() { + new instance.web.form.M2ODialog(this).open(); + }, render_editable: function() { var self = this; - this.$input = this.$element.find("input"); + this.$input = this.$el.find("input"); - self.$input.tipsy({ - title: function() { - return "No element was selected, you should create or select one from the dropdown list."; - }, - trigger:'manual', - fade: true, + this.init_error_displayer(); + + self.$input.on('focus', function() { + self.hide_error_displayer(); }); - this.$drop_down = this.$element.find(".oe_m2o_drop_down_button"); - this.$follow_button = $(".oe_m2o_cm_button", this.$element); + this.$drop_down = this.$el.find(".oe_m2o_drop_down_button"); + this.$follow_button = $(".oe_m2o_cm_button", this.$el); - this.$follow_button.click(function() { + this.$follow_button.click(function(ev) { + ev.preventDefault(); if (!self.get('value')) { self.focus(); return; } - var pop = new instance.web.form.FormOpenPopup(self.view); + var pop = new instance.web.form.FormOpenPopup(self); pop.show_element( self.field.relation, self.get("value"), @@ -2738,7 +2959,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc }); // some behavior for input - this.$input.keydown(function() { + var input_changed = function() { if (self.current_display !== self.$input.val()) { self.current_display = self.$input.val(); if (self.$input.val() === "") { @@ -2748,7 +2969,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc self.floating = true; } } - }); + }; + this.$input.keydown(input_changed); + this.$input.change(input_changed); this.$drop_down.click(function() { if (self.$input.autocomplete("widget").is(":visible")) { self.$input.autocomplete("close"); @@ -2761,10 +2984,10 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } } }); - var tip_def = $.Deferred(); - var untip_def = $.Deferred(); - var tip_delay = 200; - var tip_duration = 3000; + self.ed_def = $.Deferred(); + self.uned_def = $.Deferred(); + var ed_delay = 200; + var ed_duration = 15000; var anyoneLoosesFocus = function() { var used = false; if (self.floating) { @@ -2784,27 +3007,42 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } self.floating = false; } - if (used && self.get("value") === false) { - tip_def.reject(); - untip_def.reject(); - tip_def = $.Deferred(); - tip_def.then(function() { - self.$input.tipsy("show"); + if (used && self.get("value") === false && ! self.no_ed) { + self.ed_def.reject(); + self.uned_def.reject(); + self.ed_def = $.Deferred(); + self.ed_def.then(function() { + self.show_error_displayer(); }); setTimeout(function() { - tip_def.resolve(); - untip_def.reject(); - untip_def = $.Deferred(); - untip_def.then(function() { - self.$input.tipsy("hide"); + self.ed_def.resolve(); + self.uned_def.reject(); + self.uned_def = $.Deferred(); + self.uned_def.then(function() { + self.hide_error_displayer(); }); - setTimeout(function() {untip_def.resolve();}, tip_duration); - }, tip_delay); + setTimeout(function() {self.uned_def.resolve();}, ed_duration); + }, ed_delay); } else { - tip_def.reject(); + self.no_ed = false; + self.ed_def.reject(); } }; - this.$input.focusout(anyoneLoosesFocus); + var ignore_blur = false; + this.$input.on({ + focusout: anyoneLoosesFocus, + focus: function () { self.trigger('focused'); }, + autocompleteopen: function () { ignore_blur = true; }, + autocompleteclose: function () { ignore_blur = false; }, + blur: function () { + // autocomplete open + if (ignore_blur) { return; } + if (_(self.getChildren()).any(function (child) { + return child instanceof instance.web.form.AbstractFormPopup; + })) { return; } + self.trigger('blurred'); + } + }); var isSelecting = false; // autocomplete @@ -2822,7 +3060,6 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc self.display_value["" + item.id] = item.name; self.set({value: item.id}); } else if (item.action) { - self.floating = true; item.action(); // Cancel widget blurring, to avoid form blur event self.trigger('focused'); @@ -2847,9 +3084,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } isSelecting = false; }); - this.setupFocus(this.$input.add(this.$follow_button)); + this.setupFocus(this.$follow_button); }, - render_value: function(no_recurse) { var self = this; if (! this.get("value")) { @@ -2863,7 +3099,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } if (! no_recurse) { var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); - dataset.name_get([self.get("value")], function(data) { + dataset.name_get([self.get("value")]).then(function(data) { self.display_value["" + self.get("value")] = data[0][1]; self.render_value(true); }); @@ -2874,22 +3110,21 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc if (!this.get("effective_readonly")) { this.$input.val(str.split("\n")[0]); this.current_display = this.$input.val(); + if(this.is_false()){ + this.$('.oe_m2o_cm_button').css({'display':'none'}); + } } else { var lines = _.escape(str).split("\n"); var link = ""; var follow = ""; - if (! this.get_definition_options().highlight_first_line) { - link = lines.join("<br />"); - } else { - link = lines[0]; - follow = _.rest(lines).join("<br />"); - if (follow) - link += "<br />"; - } - var $link = this.$element.find('.oe_form_uri') + link = lines[0]; + follow = _.rest(lines).join("<br />"); + if (follow) + link += "<br />"; + var $link = this.$el.find('.oe_form_uri') .unbind('click') .html(link); - if (! this.get_definition_options().no_open) + if (! this.options.no_open) $link.click(function () { self.do_action({ type: 'ir.actions.act_window', @@ -2901,14 +3136,14 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc }); return false; }); - $(".oe_form_m2o_follow", this.$element).html(follow); + $(".oe_form_m2o_follow", this.$el).html(follow); } }, set_value: function(value_) { var self = this; if (value_ instanceof Array) { this.display_value = {}; - if (! this.get_definition_options().always_reload) { + if (! this.options.always_reload) { this.display_value["" + value_[0]] = value_[1]; } value_ = value_[0]; @@ -2930,7 +3165,17 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc }, focus: function () { this.$input.focus(); - } + }, + _quick_create: function() { + this.no_ed = true; + this.ed_def.reject(); + return instance.web.form.CompletionFieldMixin._quick_create.apply(this, arguments); + }, + _search_create_popup: function() { + this.no_ed = true; + this.ed_def.reject(); + return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); + }, }); /* @@ -2992,7 +3237,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }, start: function() { this._super.apply(this, arguments); - this.$element.addClass('oe_form_field_one2many'); + this.$el.addClass('oe_form_field oe_form_field_one2many'); var self = this; @@ -3102,7 +3347,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ } } else if (view_type === "form") { if (self.get("effective_readonly")) { - $(".oe_form_buttons", controller.$element).children().remove(); + $(".oe_form_buttons", controller.$el).children().remove(); } controller.on_record_loaded.add_last(function() { once.resolve(); @@ -3123,7 +3368,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }); this.is_setted.then(function() { $.async_when().then(function () { - self.viewmanager.appendTo(self.$element); + self.viewmanager.appendTo(self.$el); }); }); return def; @@ -3205,6 +3450,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.dataset.index = 0; } self.is_setted.resolve(); + this.trigger_on_change(); return self.reload_current_view(); }, get_value: function() { @@ -3257,7 +3503,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }, this)); }, is_syntax_valid: function() { - if (!this.viewmanager.views[this.viewmanager.active_view]) + if (! this.viewmanager || ! this.viewmanager.views[this.viewmanager.active_view]) return true; var view = this.viewmanager.views[this.viewmanager.active_view].controller; switch (this.viewmanager.active_view) { @@ -3291,12 +3537,12 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({ } var self = this; var id = self.o2m.dataset.index !== null ? self.o2m.dataset.ids[self.o2m.dataset.index] : null; - var pop = new instance.web.form.FormOpenPopup(self.o2m.view); + var pop = new instance.web.form.FormOpenPopup(this); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { title: _t("Open: ") + self.o2m.string, create_function: function(data) { return self.o2m.dataset.create(data).then(function(r) { - self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); + self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r])); self.o2m.dataset.on_change(); }); }, @@ -3334,6 +3580,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ ListType: instance.web.form.One2ManyList })); this.on('edit:before', this, this.proxy('_before_edit')); + this.on('edit:after', this, this.proxy('_after_edit')); this.on('save:before cancel:before', this, this.proxy('_before_unedit')); this.records @@ -3343,7 +3590,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ }, start: function () { var ret = this._super(); - this.$element + this.$el .off('mousedown.handleButtons') .on('mousedown.handleButtons', 'table button', this.proxy('_button_down')); return ret; @@ -3357,7 +3604,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ // If the form has not been modified, the view can only be valid // NB: is_dirty will also be set on defaults/onchanges/whatever? // oe_form_dirty seems to only be set on actual user actions - if (!form.$element.is('.oe_form_dirty')) { + if (!form.$el.is('.oe_form_dirty')) { return true; } this.o2m._dirty_flag = true; @@ -3385,7 +3632,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, create_function: function(data, callback, error_callback) { return self.o2m.dataset.create(data).then(function(r) { - self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); + self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r])); self.o2m.dataset.on_change(); }).then(callback, error_callback); }, @@ -3406,11 +3653,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ }, do_activate_record: function(index, id) { var self = this; - var pop = new instance.web.form.FormOpenPopup(self.o2m.view); + var pop = new instance.web.form.FormOpenPopup(self); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { title: _t("Open: ") + self.o2m.string, write_function: function(id, data) { - return self.o2m.dataset.write(id, data, {}, function(r) { + return self.o2m.dataset.write(id, data, {}).then(function() { self.o2m.reload_current_view(); }); }, @@ -3421,7 +3668,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); }, form_view_options: {'not_interactible_on_create':true}, - readonly: self.o2m.get("effective_readonly") + readonly: !this.is_action_enabled('edit') || self.o2m.get("effective_readonly") }); }, do_button_action: function (name, id, callback) { @@ -3434,7 +3681,10 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ var parent_form = this.o2m.view; var self = this; this.ensure_saved().pipe(function () { - return parent_form.do_save(); + if (parent_form) + return parent_form.do_save(); + else + return $.when(); }).then(function () { self.handle_button(name, id, callback); }); @@ -3444,6 +3694,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ this.__ignore_blur = false; this.editor.form.on('blurred', this, this._on_form_blur); }, + _after_edit: function () { + // The form's blur thing may be jiggered during the edition setup, + // potentially leading to the o2m instasaving the row. Cancel any + // blurring triggered the edition startup here + this.editor.form.widgetFocused(); + }, _before_unedit: function () { this.editor.form.off('blurred', this, this._on_form_blur); }, @@ -3465,7 +3721,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ return; } // FIXME: why isn't there an API for this? - if (this.editor.form.$element.hasClass('oe_form_dirty')) { + if (this.editor.form.$el.hasClass('oe_form_dirty')) { this.save_edition(); return; } @@ -3477,11 +3733,37 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ // the current row *anyway*, then create a new one/edit the next one) this.__ignore_blur = true; this._super.apply(this, arguments); + }, + do_delete: function (ids) { + var self = this; + var next = $.when(); + var _super = this._super; + // handle deletion of an item which does not exist + // TODO: better handle that in the editable list? + var false_id_index = _(ids).indexOf(false); + if (false_id_index !== -1) { + ids.splice(false_id_index, 1); + next = this.cancel_edition(true); + } + return next.pipe(function () { + // wheeee + var confirm = window.confirm; + window.confirm = function () { return true; }; + try { + return _super.call(self, ids); + } finally { + window.confirm = confirm; + } + }); } }); instance.web.form.One2ManyList = instance.web.ListView.List.extend({ pad_table_to: function (count) { - this._super(count > 0 ? count - 1 : 0); + if (!this.view.is_action_enabled('create')) { + this._super(count); + } else { + this._super(count > 0 ? count - 1 : 0); + } // magical invocation of wtf does that do if (this.view.o2m.get('effective_readonly')) { @@ -3494,30 +3776,42 @@ instance.web.form.One2ManyList = instance.web.ListView.List.extend({ }).length; if (this.options.selectable) { columns++; } if (this.options.deletable) { columns++; } + + if (!this.view.is_action_enabled('create')) { + return; + } + var $cell = $('<td>', { colspan: columns, 'class': 'oe_form_field_one2many_list_row_add' - }).text(_t("Add a row")) - .mousedown(function () { - // FIXME: needs to be an official API somehow - if (self.view.editor.is_editing()) { - self.view.__ignore_blur = true; - } - }) - .click(function (e) { - e.preventDefault(); - e.stopPropagation(); - // FIXME: there should also be an API for that one - if (self.view.editor.form.__blur_timeout) { - clearTimeout(self.view.editor.form.__blur_timeout); - self.view.editor.form.__blur_timeout = false; - } - self.view.ensure_saved().then(function () { - self.view.do_add_record(); - }); - }); - this.$current.append( - $('<tr>').append($cell)) + }).append( + $('<a>', {href: '#'}).text(_t("Add a row")) + .mousedown(function () { + // FIXME: needs to be an official API somehow + if (self.view.editor.is_editing()) { + self.view.__ignore_blur = true; + } + }) + .click(function (e) { + e.preventDefault(); + e.stopPropagation(); + // FIXME: there should also be an API for that one + if (self.view.editor.form.__blur_timeout) { + clearTimeout(self.view.editor.form.__blur_timeout); + self.view.editor.form.__blur_timeout = false; + } + self.view.ensure_saved().then(function () { + self.view.do_add_record(); + }); + })); + + var $padding = this.$current.find('tr:not([data-id]):first'); + var $newrow = $('<tr>').append($cell); + if ($padding.length) { + $padding.before($newrow); + } else { + this.$current.append($newrow) + } } }); @@ -3564,7 +3858,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in if (this.get("effective_readonly")) return; var self = this; - self.$text = $("textarea", this.$element); + self.$text = $("textarea", this.$el); self.$text.textext({ plugins : 'tags arrow autocomplete', autocomplete: { @@ -3622,7 +3916,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in self._drop_shown = true; }); self.tags = self.$text.textext()[0].tags(); - $("textarea", this.$element).focusout(function() { + $("textarea", this.$el).focusout(function() { self.$text.trigger("setInputData", ""); }).keydown(function(e) { if (e.which === $.ui.keyCode.TAB && self._drop_shown) { @@ -3646,7 +3940,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in }, render_value: function() { var self = this; - var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context()); + var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); var values = self.get("value") var handle_names = function(data) { var indexed = {}; @@ -3656,10 +3950,10 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in data = _.map(values, function(el) { return indexed[el]; }); if (! self.get("effective_readonly")) { self.tags.containerElement().children().remove(); - $("textarea", self.$element).css("padding-left", "3px"); + $("textarea", self.$el).css("padding-left", "3px"); self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};})); } else { - self.$element.html(QWeb.render("FieldMany2ManyTag", {elements: data})); + self.$el.html(QWeb.render("FieldMany2ManyTag", {elements: data})); } }; if (! values || values.length > 0) { @@ -3687,7 +3981,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ }, start: function() { this._super.apply(this, arguments); - this.$element.addClass('oe_form_field oe_form_field_many2many'); + this.$el.addClass('oe_form_field oe_form_field_many2many'); var self = this; @@ -3750,7 +4044,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ loaded.resolve(); }); $.async_when().then(function () { - self.list_view.appendTo(self.$element); + self.list_view.appendTo(self.$el); }); return loaded; }, @@ -3878,7 +4172,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( }); this.kanban_view.do_switch_view.add_last(_.bind(this.open_popup, this)); $.async_when().then(function () { - self.kanban_view.appendTo(self.$element); + self.kanban_view.appendTo(self.$el); }); return loaded; }, @@ -3916,7 +4210,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( }); } else { var id = self.dataset.ids[self.dataset.index]; - var pop = new instance.web.form.FormOpenPopup(self.view); + var pop = new instance.web.form.FormOpenPopup(this); pop.show_element(self.field.relation, id, self.build_context(), { title: _t("Open: ") + self.string, write_function: function(id, data, options) { @@ -3962,7 +4256,7 @@ instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({ }, start: function () { var self = this; - self.$text = this.$element.find('input').css("width", "200px"); + self.$text = this.$el.find('input').css("width", "200px"); self.$text.textext({ plugins : 'arrow autocomplete', autocomplete: { @@ -4019,7 +4313,7 @@ instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({ /** * Class with everything which is common between FormOpenPopup and SelectCreatePopup. */ -instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ +instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ template: "AbstractFormPopup.render", /** * options: @@ -4049,7 +4343,7 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ this.dataset.create_function = function(data, sup) { var fct = self.options.create_function || sup; return fct.call(this, data).then(function(r) { - self.created_elements.push(r.result); + self.created_elements.push(r); }); }; this.dataset.write_function = function(id, data, options, sup) { @@ -4070,8 +4364,8 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ }, title: this.options.title || "", buttons: [{text:"tmp"}], - }, this.$element).open(); - this.$buttonpane = dialog.$element.dialog("widget").find(".ui-dialog-buttonpane").html(""); + }, this.$el).open(); + this.$buttonpane = dialog.$el.dialog("widget").find(".ui-dialog-buttonpane").html(""); this.start(); }, on_write_completed: function() {}, @@ -4094,10 +4388,13 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } - this.view_form.appendTo(this.$element.find(".oe_popup_form")); + this.view_form.appendTo(this.$el.find(".oe_popup_form")); this.view_form.on_loaded.add_last(function() { var multi_select = self.row_id === null && ! self.options.disable_multiple_selection; - self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", {multi_select: multi_select})); + self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", { + multi_select: multi_select, + readonly: self.row_id !== null && self.options.readonly, + })); var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new"); $snbutton.click(function() { $.when(self.view_form.do_save()).then(function() { @@ -4118,11 +4415,6 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ $cbutton.click(function() { self.check_exit(); }); - if (self.row_id !== null && self.options.readonly) { - $snbutton.hide(); - $sbutton.hide(); - $cbutton.text(_t("Close")); - } self.view_form.do_show(); }); }, @@ -4136,7 +4428,7 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ this.destroy(); }, destroy: function () { - this.$element.dialog('close'); + this.$el.dialog('close'); this._super(); }, }); @@ -4228,7 +4520,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend e.cancel = true; }); self.view_list.popup = self; - self.view_list.appendTo($(".oe_popup_list", self.$element)).pipe(function() { + self.view_list.appendTo($(".oe_popup_list", self.$el)).pipe(function() { self.view_list.do_show(); }).pipe(function() { self.searchview.do_search(); @@ -4246,7 +4538,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend }); }); }); - this.searchview.appendTo($(".oe_popup_list", self.$element)); + this.searchview.appendTo($(".oe_popup_list", self.$el)); }, do_search: function(domains, contexts, groupbys) { var self = this; @@ -4259,11 +4551,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend }); }, on_click_element: function(ids) { + var self = this; this.selected_ids = ids || []; if(this.selected_ids.length > 0) { - this.$element.find(".oe_selectcreatepopup-search-select").removeAttr('disabled'); + self.$buttonpane.find(".oe_selectcreatepopup-search-select").removeAttr('disabled'); } else { - this.$element.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled"); + self.$buttonpane.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled"); } }, new_object: function() { @@ -4271,7 +4564,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend this.searchview.hide(); } if (this.view_list) { - this.view_list.$element.hide(); + this.view_list.$el.hide(); } this.setup_form_view(); }, @@ -4304,7 +4597,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan var sel = this.selection.get_value(); this.m2o.field.relation = sel; this.m2o.set_value(false); - this.m2o.$element.toggle(sel !== false); + this.m2o.$el.toggle(sel !== false); } }, destroy_content: function() { @@ -4319,11 +4612,21 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan }, initialize_content: function() { var self = this; - this.selection = new instance.web.form.FieldSelection(this, { attrs: { - name: 'selection' + var fm = new instance.web.form.DefaultFieldManager(this); + fm.extend_field_desc({ + "selection": { + selection: this.field_manager.get_field_desc(this.name).selection, + type: "selection", + }, + "m2o": { + relation: null, + type: "many2one", + }, + }); + this.selection = new instance.web.form.FieldSelection(fm, { attrs: { + name: 'selection', + modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); - this.selection.view = this.view; - this.selection.set({force_readonly: this.get('effective_readonly')}); this.selection.on("change:value", this, this.on_selection_changed); this.selection.setElement(this.$(".oe_form_view_reference_selection")); this.selection.renderElement(); @@ -4332,11 +4635,10 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan .on('focused', null, function () {self.trigger('focused')}) .on('blurred', null, function () {self.trigger('blurred')}); - this.m2o = new instance.web.form.FieldMany2One(this, { attrs: { - name: 'm2o' + this.m2o = new instance.web.form.FieldMany2One(fm, { attrs: { + name: 'm2o', + modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); - this.m2o.view = this.view; - this.m2o.set({force_readonly: this.get("effective_readonly")}); this.m2o.on("change:value", this, this.data_changed); this.m2o.setElement(this.$(".oe_form_view_reference_m2o")); this.m2o.renderElement(); @@ -4376,20 +4678,6 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan this.set({'value': false}); } }, - get_field: function(name) { - if (name === "selection") { - return { - selection: this.view.fields_view.fields[this.name].selection, - type: "selection", - }; - } else if (name === "m2o") { - return { - relation: null, - type: "many2one", - }; - } - throw Exception("Should not happen"); - }, }); instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { @@ -4408,9 +4696,9 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance. this._super.apply(this, arguments); }, initialize_content: function() { - this.$element.find('input.oe_form_binary_file').change(this.on_file_change); - this.$element.find('button.oe_form_binary_file_save').click(this.on_save_as); - this.$element.find('.oe_form_binary_file_clear').click(this.on_clear); + this.$el.find('input.oe_form_binary_file').change(this.on_file_change); + this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as); + this.$el.find('.oe_form_binary_file_clear').click(this.on_clear); }, human_filesize : function(size) { var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; @@ -4427,10 +4715,10 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance. // http://deepliquid.com/projects/Jcrop/demos.php?demo=handler if ($(e.target).val() !== '') { - this.$element.find('form.oe_form_binary_form input[name=session_id]').val(this.session.session_id); - this.$element.find('form.oe_form_binary_form').submit(); - this.$element.find('.oe_form_binary_progress').show(); - this.$element.find('.oe_form_binary').hide(); + this.$el.find('form.oe_form_binary_form input[name=session_id]').val(this.session.session_id); + this.$el.find('form.oe_form_binary_form').submit(); + this.$el.find('.oe_form_binary_progress').show(); + this.$el.find('.oe_form_binary').hide(); } }, on_file_uploaded: function(size, name, content_type, file_base64) { @@ -4442,8 +4730,8 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance. this.filename = name; this.on_file_uploaded_and_valid.apply(this, arguments); } - this.$element.find('.oe_form_binary_progress').hide(); - this.$element.find('.oe_form_binary').show(); + this.$el.find('.oe_form_binary_progress').hide(); + this.$el.find('.oe_form_binary').show(); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { }, @@ -4477,9 +4765,10 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance. }, set_filename: function(value) { var filename = this.node.attrs.filename; - if (this.view.fields[filename]) { - this.view.fields[filename].set_value(value); - this.view.fields[filename].on_ui_change(); + if (filename) { + var tmp = {}; + tmp[filename] = value; + this.field_manager.set_values(tmp); } }, on_clear: function() { @@ -4497,7 +4786,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ this._super(); if (this.get("effective_readonly")) { var self = this; - this.$element.find('a').click(function() { + this.$el.find('a').click(function() { if (self.get('value')) { self.on_save_as(); } @@ -4517,12 +4806,14 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ } else { show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : ''; } - this.$element.find('input').eq(0).val(show_value); + this.$el.find('input').eq(0).val(show_value); } else { - this.$element.find('a').show(!!this.get('value')); + this.$el.find('a').show(!!this.get('value')); if (this.get('value')) { - var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || ''); - this.$element.find('a').text(show_value); + var show_value = _t("Download") + if (this.view) + show_value += " " + (this.view.datarecord[this.node.attrs.filename] || ''); + this.$el.find('a').text(show_value); } } }, @@ -4530,18 +4821,12 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ this.binary_value = true; this.set({'value': file_base64}); var show_value = name + " (" + this.human_filesize(size) + ")"; - this.$element.find('input').eq(0).val(show_value); + this.$el.find('input').eq(0).val(show_value); this.set_filename(name); }, - set_filename: function(value_) { - var filename = this.node.attrs.filename; - if (this.view.fields[filename]) { - this.view.fields[filename].set({value: value_}); - } - }, on_clear: function() { this._super.apply(this, arguments); - this.$element.find('input').eq(0).val(''); + this.$el.find('input').eq(0).val(''); this.set_filename(''); } }); @@ -4553,18 +4838,31 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ this.render_value(); }, render_value: function() { + var self = this; var url; - if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) { + if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) { url = 'data:image/png;base64,' + this.get('value'); } else if (this.get('value')) { + var id = escape(JSON.stringify(this.view.datarecord.id || null)); + var field = this.name; + if (this.options.preview_image) + field = this.options.preview_image; url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + - this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime()); + this.view.dataset.model +'&id=' + id + '&field=' + field + '&t=' + (new Date().getTime()); } else { url = "/web/static/src/img/placeholder.png"; } - var img = QWeb.render("FieldBinaryImage-img", { widget: this, url: url }); - this.$element.find('> img').remove(); - this.$element.prepend(img); + var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url })); + this.$el.find('> img').remove(); + this.$el.prepend($img); + $img.load(function() { + if (! self.options.size) + return; + $img.css("max-width", "" + self.options.size[0] + "px"); + $img.css("max-height", "" + self.options.size[1] + "px"); + $img.css("margin-left", "" + (self.options.size[0] - $img.width()) / 2 + "px"); + $img.css("margin-top", "" + (self.options.size[1] - $img.height()) / 2 + "px"); + }); }, on_file_change: function() { this.render_value(); @@ -4574,24 +4872,33 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ this.set({'value': file_base64}); this.binary_value = true; this.render_value(); + this.set_filename(name); }, on_clear: function() { this._super.apply(this, arguments); this.render_value(); + this.set_filename(''); } }); instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ template: "FieldStatus", + init: function(field_manager, node) { + this._super(field_manager, node); + this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false; + this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false; + this.selected_value = null; + }, start: function() { this._super(); - this.selected_value = null; - if (this.$element.parent().is('header')) { - this.$element.after('<div class="oe_clear"/>'); + // backward compatibility + this.loaded = new $.Deferred(); + if (this.options.clickable) { + this.$el.on('click','li',this.on_click_stage); } - // preview in start only for selection fields, because of the dynamic behavior of many2one fields. - if (this.field.type in ['selection']) { - this.render_list(); + // TODO move the following into css :after + if (this.$el.parent().is('header')) { + this.$el.after('<div class="oe_clear"/>'); } }, set_value: function(value_) { @@ -4607,97 +4914,73 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ } // trick to be sure all values are loaded in the form, therefore // enabling the evaluation of dynamic domains + self.selection = []; $.async_when().then(function() { - return self.render_list(); + self.get_selection(); }); + return this.loaded; }, - - /** Get the status list and render them - * to_show: [[identifier, value_to_display]] where - * - identifier = key for a selection, id for a many2one - * - display_val = label that will be displayed - * - ex: [[0, "New"]] (many2one) or [["new", "In Progress"]] (selection) - */ - render_list: function() { - var self = this; - // get selection values, filter them and render them - var selection_done = this.get_selection().pipe(self.proxy('filter_selection')).pipe(self.proxy('render_elements')); - }, - - /** Get the selection list to be displayed in the statusbar widget. + /** Get the selection and render it + * selection: [[identifier, value_to_display], ...] * For selection fields: this is directly given by this.field.selection - * For many2one fields : - * - perform a search on the relation of the many2one field (given by - * field.relation ) - * - get the field domain for the search - * - self.build_domain() gives the domain given by the view or by - * the field - * - if the optional statusbar_fold attribute is set to true, make - * an AND with build_domain to hide all 'fold=true' columns - * - make an OR with current value, to be sure it is displayed, - * with the correct order, even if it is folded + * For many2one fields: perform a search on the relation of the many2one field */ get_selection: function() { var self = this; if (this.field.type == "many2one") { - this.selection = []; - // get fold information from widget - var fold = ((this.node.attrs || {}).statusbar_fold || true); - // build final domain: if fold option required, add the - if (fold == true) { - var domain = new instance.web.CompoundDomain(['|'], ['&'], self.build_domain(), [['fold', '=', false]], [['id', '=', self.selected_value]]); - } else { - var domain = new instance.web.CompoundDomain(['|'], self.build_domain(), [['id', '=', self.selected_value]]); + var domain = []; + if(!_.isEmpty(this.field.domain) || !_.isEmpty(this.node.attrs.domain)) { + domain = new instance.web.CompoundDomain(['|'], self.build_domain(), [['id', '=', self.selected_value]]); } - // get a DataSetSearch on the current field relation (ex: crm.lead.stage_id -> crm.case.stage) - var model_ext = new instance.web.DataSetSearch(this, this.field.relation, self.build_context(), domain); - // fetch selection - var read_defer = model_ext.read_slice(['name'], {}).pipe( function (records) { - _(records).each(function (record) { - self.selection.push([record.id, record.name]); - }); + var ds = new instance.web.DataSetSearch(this, this.field.relation, self.build_context(), domain); + ds.read_slice(['name'], {}).done( function (records) { + for(var i = 0; i < records.length; i++) { + self.selection.push([records[i].id, records[i].name]); + } + self.render_elements(); + self.loaded.resolve(); }); } else { - this.selection = this.field.selection; - var read_defer = new $.Deferred().resolve(); - } - return read_defer; - }, - - /** Filters this.selection, according to values coming from the statusbar_visible - * attribute of the field. For example: statusbar_visible="draft,open" - * Currently, the key of (key, label) pairs has to be used in the - * selection of visible items. This feature is not meant to be used - * with many2one fields. - */ - filter_selection: function() { - var self = this; - var shown = _.map(((this.node.attrs || {}).statusbar_visible || "").split(","), - function(x) { return _.str.trim(x); }); - shown = _.select(shown, function(x) { return x.length > 0; }); - - if (shown.length == 0) { - this.to_show = this.selection; - } else { - this.to_show = _.select(this.selection, function(x) { - return _.indexOf(shown, x[0]) !== -1 || x[0] === self.selected_value; - }); + this.loaded.resolve(); + // For field type selection filter values according to + // statusbar_visible attribute of the field. For example: + // statusbar_visible="draft,open". + var selection = this.field.selection; + for(var i=0; i< selection.length; i++) { + var key = selection[i][0]; + if(key == this.selected_value || !this.options.visible || this.options.visible.indexOf(key) != -1) { + this.selection.push(selection[i]); + } + } + this.render_elements(); } }, - /** Renders the widget. This function also checks for statusbar_colors='{"pending": "blue"}' * attribute in the widget. This allows to set a given color to a given * state (given by the key of (key, label)). */ render_elements: function () { - var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_}); - this.$element.html(content); - + var self = this; + var content = instance.web.qweb.render("FieldStatus.content", {widget: this}); + this.$el.html(content); var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}"); var color = colors[this.selected_value]; if (color) { - var elem = this.$element.find("li.oe_form_steps_active span"); - elem.css("color", color); + this.$("oe_active").css("color", color); + } + }, + on_click_stage: function (ev) { + var self = this; + var $li = $(ev.currentTarget); + var val = parseInt($li.data("id")); + if (val != self.selected_value) { + this.view.recursive_save().then(function() { + var change = {}; + change[self.name] = val; + self.view.dataset.write(self.view.datarecord.id, change).then(function() { + self.view.reload(); + }); + }); } }, }); @@ -4714,7 +4997,7 @@ instance.web.form.widgets = new instance.web.Registry({ 'email' : 'instance.web.form.FieldEmail', 'url' : 'instance.web.form.FieldUrl', 'text' : 'instance.web.form.FieldText', - 'text_html' : 'instance.web.form.FieldTextHtml', + 'html' : 'instance.web.form.FieldTextHtml', 'date' : 'instance.web.form.FieldDate', 'datetime' : 'instance.web.form.FieldDatetime', 'selection' : 'instance.web.form.FieldSelection', @@ -4745,6 +5028,9 @@ instance.web.form.tags = new instance.web.Registry({ 'button' : 'instance.web.form.WidgetButton', }); +instance.web.form.custom_widgets = new instance.web.Registry({ +}); + }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 57be3ca6db1..fd34ffddb7b 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -21,8 +21,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi // whether the view rows can be reordered (via vertical drag & drop) 'reorderable': true, 'action_buttons': true, - // if true, the 'Import', 'Export', etc... buttons will be shown - 'import_enabled': true, }, /** * Core class for list-type displays. @@ -145,7 +143,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @returns {$.Deferred} loading promise */ start: function() { - this.$element.addClass('oe_list'); + this.$el.addClass('oe_list'); return this.reload_view(null, null, true); }, /** @@ -248,20 +246,26 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi this.setup_columns(this.fields_view.fields, grouped); - this.$element.html(QWeb.render(this._template, this)); - this.$element.addClass(this.fields_view.arch.attrs['class']); + this.$el.html(QWeb.render(this._template, this)); + this.$el.addClass(this.fields_view.arch.attrs['class']); + + // add css classes that reflect the (absence of) access rights + this.$el.toggleClass('oe_list_cannot_create', !this.is_action_enabled('create')) + .toggleClass('oe_list_cannot_edit', !this.is_action_enabled('edit')) + .toggleClass('oe_list_cannot_delete', !this.is_action_enabled('delete')); + // Head hook // Selecting records - this.$element.find('.oe_list_record_selector').click(function(){ - self.$element.find('.oe_list_record_selector input').prop('checked', - self.$element.find('.oe_list_record_selector').prop('checked') || false); + this.$el.find('.oe_list_record_selector').click(function(){ + self.$el.find('.oe_list_record_selector input').prop('checked', + self.$el.find('.oe_list_record_selector').prop('checked') || false); var selection = self.groups.get_selection(); $(self.groups).trigger( 'selected', [selection.ids, selection.records]); }); // Sorting columns - this.$element.find('thead').delegate('th.oe_sortable[data-id]', 'click', function (e) { + this.$el.find('thead').delegate('th.oe_sortable[data-id]', 'click', function (e) { e.stopPropagation(); var $this = $(this); self.dataset.sort($this.data('id')); @@ -275,21 +279,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi self.reload_content(); }); - // Add button and Import link + // Add button if (!this.$buttons) { this.$buttons = $(QWeb.render("ListView.buttons", {'widget':self})); if (this.options.$buttons) { this.$buttons.appendTo(this.options.$buttons); } else { - this.$element.find('.oe_list_buttons').replaceWith(this.$buttons); + this.$el.find('.oe_list_buttons').replaceWith(this.$buttons); } this.$buttons.find('.oe_list_add') .click(this.proxy('do_add_record')) .prop('disabled', grouped); - this.$buttons.on('click', '.oe_list_button_import', function() { - self.on_sidebar_import(); - return false; - }); } // Pager @@ -298,7 +298,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (this.options.$buttons) { this.$pager.appendTo(this.options.$pager); } else { - this.$element.find('.oe_list_pager').replaceWith(this.$pager); + this.$el.find('.oe_list_pager').replaceWith(this.$pager); } this.$pager @@ -351,13 +351,12 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (!this.sidebar && this.options.$sidebar) { this.sidebar = new instance.web.Sidebar(this); this.sidebar.appendTo(this.options.$sidebar); - this.sidebar.add_items('other', [ - { label: _t("Import"), callback: this.on_sidebar_import }, + this.sidebar.add_items('other', _.compact([ { label: _t("Export"), callback: this.on_sidebar_export }, - { label: _t('Delete'), callback: this.do_delete_selected } - ]); + self.is_action_enabled('delete') && { label: _t('Delete'), callback: this.do_delete_selected } + ])); this.sidebar.add_toolbar(this.fields_view.toolbar); - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } }, /** @@ -399,73 +398,26 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed */ setup_columns: function (fields, grouped) { - var domain_computer = instance.web.form.compute_domain; - - var noop = function () { return {}; }; - var field_to_column = function (field) { - var name = field.attrs.name; - var column = _.extend({id: name, tag: field.tag}, - fields[name], field.attrs); - // modifiers computer - if (column.modifiers) { - var modifiers = JSON.parse(column.modifiers); - column.modifiers_for = function (fields) { - var out = {}; - - for (var attr in modifiers) { - if (!modifiers.hasOwnProperty(attr)) { continue; } - var modifier = modifiers[attr]; - out[attr] = _.isBoolean(modifier) - ? modifier - : domain_computer(modifier, fields); - } - - return out; - }; - if (modifiers['tree_invisible']) { - column.invisible = '1'; - } else { - delete column.invisible; - } - column.modifiers = modifiers; - } else { - column.modifiers_for = noop; - column.modifiers = {}; - } - return column; - }; - + var registry = instance.web.list.columns; + var reorder = this.options.reorderable; this.columns.splice(0, this.columns.length); - this.columns.push.apply( - this.columns, - _(this.fields_view.arch.children).map(field_to_column)); + this.columns.push.apply(this.columns, + _(this.fields_view.arch.children).map(function (field) { + var id = field.attrs.name; + if(field.attrs.widget == 'handle' && !reorder){ + field.attrs.reorderable = reorder || true; + } + return registry.for_(id, fields[id], field); + })); if (grouped) { - this.columns.unshift({ - id: '_group', tag: '', string: _t("Group"), meta: true, - modifiers_for: function () { return {}; }, - modifiers: {} - }); + this.columns.unshift( + new instance.web.list.MetaColumn('_group', _t("Group"))); } this.visible_columns = _.filter(this.columns, function (column) { - return column.invisible !== '1'; + return column.invisible !== '1' && !column.reorderable; }); - - this.aggregate_columns = _(this.visible_columns) - .map(function (column) { - if (column.type !== 'integer' && column.type !== 'float') { - return {}; - } - var aggregation_func = column['group_operator'] || 'sum'; - if (!(aggregation_func in column)) { - return {}; - } - - return _.extend({}, column, { - 'function': aggregation_func, - label: column[aggregation_func] - }); - }); + this.aggregate_columns = _(this.visible_columns).invoke('to_aggregate'); }, /** * Used to handle a click on a table row, if no other handler caught the @@ -500,7 +452,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }, do_hide: function () { if (this.sidebar) { - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } if (this.$buttons) { this.$buttons.hide(); @@ -524,7 +476,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (this.embedded_view) { return $.Deferred().then(callback).resolve(this.embedded_view); } else { - return this.rpc('/web/listview/load', { + return this.rpc('/web/view/load', { model: this.model, view_id: this.view_id, view_type: "tree", @@ -540,10 +492,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi */ reload_content: function () { var self = this; - self.$element.find('.oe_list_record_selector').prop('checked', false); + self.$el.find('.oe_list_record_selector').prop('checked', false); this.records.reset(); var reloaded = $.Deferred(); - this.$element.find('.oe_list_content').append( + this.$el.find('.oe_list_content').append( this.groups.render(function () { if (self.dataset.index == null) { var has_one = false; @@ -645,7 +597,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (!ids.length) { this.dataset.index = 0; if (this.sidebar) { - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } this.compute_aggregates(); return; @@ -653,7 +605,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi this.dataset.index = _(this.dataset.ids).indexOf(ids[0]); if (this.sidebar) { - this.sidebar.$element.show(); + this.sidebar.$el.show(); } this.compute_aggregates(_(records).map(function (record) { @@ -674,6 +626,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * Base handling of buttons, can be called when overriding do_button_action * in order to bypass parent overrides. * + * The callback will be provided with the ``id`` as its parameter, in case + * handle_button's caller had to alter the ``id`` (or even create one) + * while not being ``callback``'s creator. + * * This method should not be overridden. * * @param {String} name action name @@ -699,7 +655,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi c.add(action.context); } action.context = c; - this.do_execute_action(action, this.dataset, id, callback); + this.do_execute_action( + action, this.dataset, id, _.bind(callback, null, id)); }, /** * Handles the activation of a record (clicking on it) @@ -808,16 +765,14 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }, display_aggregates: function (aggregation) { var self = this; - var $footer_cells = this.$element.find('.oe_list_footer'); + var $footer_cells = this.$el.find('.oe_list_footer'); _(this.aggregate_columns).each(function (column) { if (!column['function']) { return; } $footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id)) - .html(instance.web.format_cell(aggregation, column, { - process_modifiers: false - })); + .html(column.format(aggregation, { process_modifiers: false })); }); }, get_selected_ids: function() { @@ -836,7 +791,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi pad_columns: function (count, options) { options = options || {}; // padding for action/pager header - var $first_header = this.$element.find('thead tr:first th'); + var $first_header = this.$el.find('thead tr:first th'); var colspan = $first_header.attr('colspan'); if (colspan) { if (!this.previous_colspan) { @@ -845,7 +800,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi $first_header.attr('colspan', parseInt(colspan, 10) + count); } // Padding for column titles, footer and data rows - var $rows = this.$element + var $rows = this.$el .find('.oe_list_header_columns, tr:not(thead tr)') .not(options['except']); var newcols = new Array(count+1).join('<td class="oe_list_padding"></td>'); @@ -859,25 +814,29 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * Removes all padding columns of the table */ unpad_columns: function () { - this.$element.find('.oe_list_padding').remove(); + this.$el.find('.oe_list_padding').remove(); if (this.previous_colspan) { - this.$element + this.$el .find('thead tr:first th') .attr('colspan', this.previous_colspan); this.previous_colspan = null; } }, no_result: function () { - this.$element.find('.oe_view_nocontent').remove(); + this.$el.find('.oe_view_nocontent').remove(); if (this.groups.group_by || !this.options.action || !this.options.action.help) { return; } - this.$element.find('table:first').hide(); - this.$element.prepend( + this.$el.find('table:first').hide(); + this.$el.prepend( $('<div class="oe_view_nocontent">').html(this.options.action.help) ); + var create_nocontent = this.$buttons; + this.$el.find('.oe_view_nocontent').click(function() { + create_nocontent.effect('bounce', {distance: 18, times: 5}, 150); + }); } }); instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.ListView.List# */{ @@ -967,8 +926,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. this.records.bind(event, callback); }, this); - this.$_element = $('<tbody>') - .appendTo(document.body) + this.$current = $('<tbody>') .delegate('th.oe_list_record_selector', 'click', function (e) { e.stopPropagation(); var selection = self.get_selection(); @@ -991,8 +949,8 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // of digits, nice when storing actual numbers, not nice when // storing strings composed only of digits. Force the action // name to be a string - $(self).trigger('action', [field.toString(), record_id, function () { - return self.reload_record(self.records.get(record_id)); + $(self).trigger('action', [field.toString(), record_id, function (id) { + return self.reload_record(self.records.get(id)); }]); }) .delegate('a', 'click', function (e) { @@ -1031,7 +989,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // to get a correctly displayable value in the field var model = ref_match[1], id = parseInt(ref_match[2], 10); - new instance.web.DataSet(this.view, model).name_get([id], function(names) { + new instance.web.DataSet(this.view, model).name_get([id]).then(function(names) { if (!names.length) { return; } record.set(column.id, names[0][1]); }); @@ -1047,23 +1005,40 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // and let the various registered events handle refreshing the // row new instance.web.DataSet(this.view, column.relation) - .name_get([value], function (names) { + .name_get([value]).then(function (names) { if (!names.length) { return; } record.set(column.id, names[0]); }); } + } else if (column.type === 'many2many') { + value = record.get(column.id); + // non-resolved (string) m2m values are arrays + if (value instanceof Array && !_.isEmpty(value)) { + var ids; + // they come in two shapes: + if (value[0] instanceof Array) { + var command = value[0]; + // 1. an array of m2m commands (usually (6, false, ids)) + if (command[0] !== 6) { + throw new Error(_t("Unknown m2m command ") + command[0]); + } + ids = command[2]; + } else { + // 2. an array of ids + ids = value; + } + new instance.web.Model(column.relation) + .call('name_get', [ids]).then(function (names) { + record.set(column.id, _(names).pluck(1).join(', ')); + }) + } } - return instance.web.format_cell(record.toForm().data, column, { + return column.format(record.toForm().data, { model: this.dataset.model, id: record.get('id') }); }, render: function () { - var self = this; - if (this.$current) { - this.$current.remove(); - } - this.$current = this.$_element.clone(true); this.$current.empty().append( QWeb.render('ListView.rows', _.extend({ render_cell: function () { @@ -1139,7 +1114,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. if (!this.$current) { return; } this.$current.remove(); this.$current = null; - this.$_element.remove(); }, get_records: function () { return this.records.map(function (record) { @@ -1306,7 +1280,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we self.bind_child_events(child); child.datagroup = group; - var $row = child.$row = $('<tr>'); + var $row = child.$row = $('<tr class="oe_group_header">'); if (group.openable && group.length) { $row.click(function (e) { if (!$row.data('open')) { @@ -1340,10 +1314,9 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we } var group_label; try { - group_label = instance.web.format_cell( - row_data, group_column, { - value_if_empty: _t("Undefined"), - process_modifiers: false + group_label = group_column.format(row_data, { + value_if_empty: _t("Undefined"), + process_modifiers: false }); } catch (e) { group_label = row_data[group_column.id].value; @@ -1367,7 +1340,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we $row.append('<td>'); } _(self.columns).chain() - .filter(function (column) {return !column.invisible;}) + .filter(function (column) { return column.invisible !== '1'; }) .each(function (column) { if (column.meta) { // do not do anything @@ -1375,8 +1348,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we var r = {}; r[column.id] = {value: group.aggregates[column.id]}; $('<td class="oe_number">') - .html(instance.web.format_cell( - r, column, {process_modifiers: false})) + .html(column.format(r, {process_modifiers: false})) .appendTo($row); } else { $row.append('<td>'); @@ -1463,18 +1435,31 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we }, setup_resequence_rows: function (list, dataset) { // drag and drop enabled if list is not sorted and there is a - // "sequence" column in the view. + // visible column with @widget=handle or "sequence" column in the view. if ((dataset.sort && dataset.sort()) || !_(this.columns).any(function (column) { - return column.name === 'sequence'; })) { + return column.widget === 'handle' + || column.name === 'sequence'; })) { return; } + var sequence_field = _(this.columns).find(function (c) { + return c.widget === 'handle'; + }); + var seqname = sequence_field ? sequence_field.name : 'sequence'; + // ondrop, move relevant record & fix sequences list.$current.sortable({ axis: 'y', items: '> tr[data-id]', - containment: 'parent', - helper: 'clone', + helper: 'clone' + }); + if (sequence_field) { + list.$current.sortable('option', 'handle', '.oe_list_field_handle'); + } + list.$current.sortable('option', { + start: function (e, ui) { + ui.placeholder.height(ui.item.height()); + }, stop: function (event, ui) { var to_move = list.records.get(ui.item.data('id')), target_id = ui.item.prev().data('id'), @@ -1492,7 +1477,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we var record, index = to, // if drag to 1st row (to = 0), start sequencing from 0 // (exclusive lower bound) - seq = to ? list.records.at(to - 1).get('sequence') : 0; + seq = to ? list.records.at(to - 1).get(seqname) : 0; while (++seq, record = list.records.at(index++)) { // write are independent from one another, so we can just // launch them all at the same time and we don't really @@ -1502,37 +1487,39 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we // when synchronous (without setTimeout) (function (dataset, id, seq) { $.async_when().then(function () { - dataset.write(id, {sequence: seq}); + var attrs = {}; + attrs[seqname] = seq; + dataset.write(id, attrs); }); }(dataset, record.get('id'), seq)); - record.set('sequence', seq); + record.set(seqname, seq); } } }); }, render: function (post_render) { var self = this; - var $element = $('<tbody>'); - this.elements = [$element[0]]; + var $el = $('<tbody>'); + this.elements = [$el[0]]; this.datagroup.list( _(this.view.visible_columns).chain() .filter(function (column) { return column.tag === 'field' }) .pluck('name').value(), function (groups) { - $element[0].appendChild( + $el[0].appendChild( self.render_groups(groups)); if (post_render) { post_render(); } }, function (dataset) { self.render_dataset(dataset).then(function (list) { self.children[null] = list; self.elements = - [list.$current.replaceAll($element)[0]]; + [list.$current.replaceAll($el)[0]]; self.setup_resequence_rows(list, dataset); if (post_render) { post_render(); } }); }); - return $element; + return $el; }, /** * Returns the ids of all selected records for this group, and the records @@ -1959,6 +1946,213 @@ instance.web.list = { Events: Events, Record: Record, Collection: Collection -} +}; +/** + * Registry for column objects used to format table cells (and some other tasks + * e.g. aggregation computations). + * + * Maps a field or button to a Column type via its ``$tag.$widget``, + * ``$tag.$type`` or its ``$tag`` (alone). + * + * This specific registry has a dedicated utility method ``for_`` taking a + * field (from fields_get/fields_view_get.field) and a node (from a view) and + * returning the right object *already instantiated from the data provided*. + * + * @type {instance.web.Registry} + */ +instance.web.list.columns = new instance.web.Registry({ + 'field': 'instance.web.list.Column', + 'field.boolean': 'instance.web.list.Boolean', + 'field.binary': 'instance.web.list.Binary', + 'field.progressbar': 'instance.web.list.ProgressBar', + 'field.handle': 'instance.web.list.Handle', + 'button': 'instance.web.list.Button', +}); +instance.web.list.columns.for_ = function (id, field, node) { + var description = _.extend({tag: node.tag}, field, node.attrs); + var tag = description.tag; + var Type = this.get_any([ + tag + '.' + description.widget, + tag + '.'+ description.type, + tag + ]); + return new Type(id, node.tag, description) +}; + +instance.web.list.Column = instance.web.Class.extend({ + init: function (id, tag, attrs) { + _.extend(attrs, { + id: id, + tag: tag + }); + + this.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; + delete attrs.modifiers; + _.extend(this, attrs); + + if (this.modifiers['tree_invisible']) { + this.invisible = '1'; + } else { delete this.invisible; } + }, + modifiers_for: function (fields) { + var out = {}; + var domain_computer = instance.web.form.compute_domain; + + for (var attr in this.modifiers) { + if (!this.modifiers.hasOwnProperty(attr)) { continue; } + var modifier = this.modifiers[attr]; + out[attr] = _.isBoolean(modifier) + ? modifier + : domain_computer(modifier, fields); + } + + return out; + }, + to_aggregate: function () { + if (this.type !== 'integer' && this.type !== 'float') { + return {}; + } + var aggregation_func = this['group_operator'] || 'sum'; + if (!(aggregation_func in this)) { + return {}; + } + var C = function (fn, label) { + this['function'] = fn; + this.label = label; + }; + C.prototype = this; + return new C(aggregation_func, this[aggregation_func]); + }, + /** + * + * @param row_data record whose values should be displayed in the cell + * @param {Object} [options] + * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false`` + * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ? + * @param {String} [options.model] current record's model + * @param {Number} [options.id] current record's id + * @return {String} + */ + format: function (row_data, options) { + options = options || {}; + var attrs = {}; + if (options.process_modifiers !== false) { + attrs = this.modifiers_for(row_data); + } + if (attrs.invisible) { return ''; } + + if (!row_data[this.id]) { + return options.value_if_empty === undefined + ? '' + : options.value_if_empty; + } + return this._format(row_data, options); + }, + /** + * Method to override in order to provide alternative HTML content for the + * cell. Column._format will simply call ``instance.web.format_value`` and + * escape the output. + * + * The output of ``_format`` will *not* be escaped by ``format``, any + * escaping *must be done* by ``format``. + * + * @private + */ + _format: function (row_data, options) { + return _.escape(instance.web.format_value( + row_data[this.id].value, this, options.value_if_empty)); + } +}); +instance.web.list.MetaColumn = instance.web.list.Column.extend({ + meta: true, + init: function (id, string) { + this._super(id, '', {string: string}); + } +}); +instance.web.list.Button = instance.web.list.Column.extend({ + /** + * Return an actual ``<button>`` tag + */ + format: function (row_data, options) { + options = options || {}; + var attrs = {}; + if (options.process_modifiers !== false) { + attrs = this.modifiers_for(row_data); + } + if (attrs.invisible) { return ''; } + + return QWeb.render('ListView.row.button', { + widget: this, + prefix: instance.session.prefix, + disabled: attrs.readonly + || isNaN(row_data.id.value) + || instance.web.BufferedDataSet.virtual_id_regex.test(row_data.id.value) + }); + } +}); +instance.web.list.Boolean = instance.web.list.Column.extend({ + /** + * Return a potentially disabled checkbox input + * + * @private + */ + _format: function (row_data, options) { + return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>', + row_data[this.id].value ? 'checked="checked"' : ''); + } +}); +instance.web.list.Binary = instance.web.list.Column.extend({ + /** + * Return a link to the binary data as a file + * + * @private + */ + _format: function (row_data, options) { + var text = _t("Download"); + var download_url = _.str.sprintf( + '/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', + instance.session.session_id, options.model, this.id, options.id); + if (this.filename) { + download_url += '&filename_field=' + this.filename; + if (row_data[this.filename]) { + text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value( + row_data[this.filename].value, {type: 'char'})); + } + } + return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', { + text: text, + href: download_url, + size: row_data[this.id].value + }); + } +}); +instance.web.list.ProgressBar = instance.web.list.Column.extend({ + /** + * Return a formatted progress bar display + * + * @private + */ + _format: function (row_data, options) { + return _.template( + '<progress value="<%-value%>" max="100"><%-value%>%</progress>', { + value: _.str.sprintf("%.0f", row_data[this.id].value || 0) + }); + } +}); +instance.web.list.Handle = instance.web.list.Column.extend({ + init: function () { + this._super.apply(this, arguments); + // Handle overrides the field to not be form-editable. + this.modifiers.readonly = true; + }, + /** + * Return styling hooks for a drag handle + * + * @private + */ + _format: function (row_data, options) { + return '<div class="oe_list_handle">'; + } +}); }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 038593c54c3..892221a347b 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -45,16 +45,22 @@ openerp.web.list_editable = function (instance) { } }); this.on('edit:after', this, function () { - self.$element.add(self.$buttons).addClass('oe_editing'); + self.$el.add(self.$buttons).addClass('oe_editing'); }); this.on('save:after cancel:after', this, function () { - self.$element.add(self.$buttons).removeClass('oe_editing'); + self.$el.add(self.$buttons).removeClass('oe_editing'); }); }, destroy: function () { instance.web.bus.off('resize', this, this.resize_fields); this._super(); }, + do_hide: function () { + if (this.editor.is_editing()) { + this.cancel_edition(true); + } + this._super(); + }, /** * Handles the activation of a record in editable mode (making a record * editable), called *after* the record has become editable. @@ -87,8 +93,8 @@ openerp.web.list_editable = function (instance) { */ do_add_record: function () { if (this.editable()) { - this.$element.find('table:first').show(); - this.$element.find('.oe_view_nocontent').remove(); + this.$el.find('table:first').show(); + this.$el.find('.oe_view_nocontent').remove(); this.start_edition(); } else { this._super(); @@ -99,6 +105,7 @@ openerp.web.list_editable = function (instance) { // tree/@editable takes priority on everything else if present. var result = this._super(data, grouped); if (this.editable()) { + this.$el.addClass('oe_list_editable'); // FIXME: any hook available to ensure this is only done once? this.$buttons .off('click', '.oe_list_save') @@ -108,7 +115,7 @@ openerp.web.list_editable = function (instance) { e.preventDefault(); self.cancel_edition(); }); - this.$element + this.$el .off('click', 'tbody td:not(.oe_list_field_cell)') .on('click', 'tbody td:not(.oe_list_field_cell)', function () { if (!self.editor.is_editing()) { @@ -119,10 +126,12 @@ openerp.web.list_editable = function (instance) { // Editor is not restartable due to formview not being // restartable this.editor = this.make_editor(); - var editor_ready = this.editor.prependTo(this.$element) + var editor_ready = this.editor.prependTo(this.$el) .then(this.proxy('setup_events')); return $.when(result, editor_ready); + } else { + this.$el.removeClass('oe_list_editable'); } return result; @@ -135,10 +144,13 @@ openerp.web.list_editable = function (instance) { make_editor: function () { return new instance.web.list.Editor(this); }, - do_button_action: function () { + do_button_action: function (name, id, callback) { var self = this, args = arguments; - this.ensure_saved().then(function () { - self.handle_button.apply(self, args); + this.ensure_saved().then(function (done) { + if (!id && done.created) { + id = done.record.get('id'); + } + self.handle_button.call(self, name, id, callback); }); }, /** @@ -197,7 +209,7 @@ openerp.web.list_editable = function (instance) { } // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow - field.$element.attr('data-fieldname', field_name); + field.$el.attr('data-fieldname', field_name); self.fields_for_resize.push({field: field, cell: cell}); }, options).pipe(function () { $recordRow.addClass('oe_edition'); @@ -242,10 +254,11 @@ openerp.web.list_editable = function (instance) { var $cell = $(cell); var position = $cell.position(); - field.$element.css({ + // jquery does not understand !important + field.$el.attr('style', 'width: '+$cell.outerWidth()+'px !important'); + field.$el.css({ top: position.top, left: position.left, - width: $cell.outerWidth(), minHeight: $cell.outerHeight() }); }, @@ -281,16 +294,17 @@ openerp.web.list_editable = function (instance) { }); }, /** + * @param {Boolean} [force=false] discards the data even if the form has been edited * @return {jQuery.Deferred} */ - cancel_edition: function () { + cancel_edition: function (force) { var self = this; return this.with_event('cancel', { editor: this.editor, form: this.editor.form, cancel: false }, function () { - return this.editor.cancel().pipe(function (attrs) { + return this.editor.cancel(force).pipe(function (attrs) { if (attrs.id) { var record = self.records.get(attrs.id); if (!record) { @@ -346,14 +360,17 @@ openerp.web.list_editable = function (instance) { 'class': 'oe_form_container', version: '7.0' }); - _(view.arch.children).each(function (widget) { - var modifiers = JSON.parse(widget.attrs.modifiers || '{}'); - widget.attrs.nolabel = true; - if (modifiers['tree_invisible'] || widget.tag === 'button') { - modifiers.invisible = true; - } - widget.attrs.modifiers = JSON.stringify(modifiers); - }); + _(view.arch.children).chain() + .zip(this.columns) + .each(function (ar) { + var widget = ar[0], column = ar[1]; + var modifiers = _.extend({}, column.modifiers); + widget.attrs.nolabel = true; + if (modifiers['tree_invisible'] || widget.tag === 'button') { + modifiers.invisible = true; + } + widget.attrs.modifiers = JSON.stringify(modifiers); + }); return view; }, handle_onwrite: function (source_record) { @@ -383,7 +400,7 @@ openerp.web.list_editable = function (instance) { }, setup_events: function () { var self = this; - this.editor.$element.on('keyup keydown', function (e) { + this.editor.$el.on('keyup keydown', function (e) { if (!self.editor.is_editing()) { return; } var key = _($.ui.keyCode).chain() .map(function (v, k) { return {name: k, code: v}; }) @@ -532,7 +549,7 @@ openerp.web.list_editable = function (instance) { if (--field_index < 0) { return $.when(); } field = fields[fields_order[field_index]]; - } while (!field.$element.is(':visible')); + } while (!field.$el.is(':visible')); // and focus it field.focus(); @@ -555,7 +572,7 @@ openerp.web.list_editable = function (instance) { if (++field_index >= fields_order.length) { return $.when(); } field = fields[fields_order[field_index]]; - } while (!field.$element.is(':visible')); + } while (!field.$el.is(':visible')); field.focus(); return $.when(); @@ -564,11 +581,11 @@ openerp.web.list_editable = function (instance) { var form = this.editor.form; var last_field = _(form.fields_order).chain() .map(function (name) { return form.fields[name]; }) - .filter(function (field) { return field.$element.is(':visible'); }) + .filter(function (field) { return field.$el.is(':visible'); }) .last() .value(); // tabbed from last field in form - if (last_field && last_field.$element.has(e.target).length) { + if (last_field && last_field.$el.has(e.target).length) { e.preventDefault(); return this._next(); } @@ -612,7 +629,7 @@ openerp.web.list_editable = function (instance) { var _super = this._super(); this.form.embedded_view = this._validate_view( this.delegate.edition_view(this)); - var form_ready = this.form.appendTo(this.$element).then( + var form_ready = this.form.appendTo(this.$el).then( self.form.proxy('do_hide')); return $.when(_super, form_ready); }, @@ -670,7 +687,7 @@ openerp.web.list_editable = function (instance) { // Is actually in the form && (field = form.fields[focus_field]) // And is visible - && field.$element.is(':visible')) { + && field.$el.is(':visible')) { // focus it field.focus(); return; @@ -679,7 +696,7 @@ openerp.web.list_editable = function (instance) { _(form.fields_order).detect(function (name) { // look for first visible field in fields_order, focus it var field = form.fields[name]; - if (!field.$element.is(':visible')) { + if (!field.$el.is(':visible')) { return false; } // Stop as soon as a field got focused @@ -708,7 +725,7 @@ openerp.web.list_editable = function (instance) { save: function () { var self = this; return this.form - .do_save(null, this.delegate.prepends_on_create()) + .do_save(this.delegate.prepends_on_create()) .pipe(function (result) { var created = result.created && !self.record.id; if (created) { @@ -717,13 +734,13 @@ openerp.web.list_editable = function (instance) { return self.cancel(); }); }, - cancel: function () { - var record = this.record; - this.record = null; - if (!this.form.can_be_discarded()) { + cancel: function (force) { + if (!(force || this.form.can_be_discarded())) { return $.Deferred().reject({ message: "The form's data can not be discarded"}).promise(); } + var record = this.record; + this.record = null; this.form.do_hide(); return $.when(record); } @@ -742,7 +759,7 @@ openerp.web.list_editable = function (instance) { instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{ row_clicked: function (event) { - if (!this.view.editable()) { + if (!this.view.editable() || ! this.view.is_action_enabled('edit')) { return this._super.apply(this, arguments); } var record_id = $(event.currentTarget).data('id'); diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 2181b2aa157..e0493a446e2 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -84,13 +84,13 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie }); this.fields = fields_view.fields; this.hook_row_click(); - this.$element.html(QWeb.render('TreeView', { + this.$el.html(QWeb.render('TreeView', { 'title': this.fields_view.arch.attrs.string, 'fields_view': this.fields_view.arch.children, 'fields': this.fields, 'toolbar': has_toolbar })); - this.$element.addClass(this.fields_view.arch.attrs['class']); + this.$el.addClass(this.fields_view.arch.attrs['class']); this.dataset.read_slice(this.fields_list()).then(function(records) { self.store_record(records); @@ -102,7 +102,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie return; } - var $select = self.$element.find('select') + var $select = self.$el.find('select') .change(function () { var $option = $(this).find(':selected'); self.getdata($option.val()); @@ -120,10 +120,10 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie $select.change(); } }); - this.$element.find("#tree_view_expand").click(function(){ + this.$el.find("#tree_view_expand").click(function(){ self.expand_all(); }); - this.$element.find("#tree_view_collapse").click(function(){ + this.$el.find("#tree_view_collapse").click(function(){ self.collpase_all(); }); // TODO store open nodes in url ?... @@ -143,14 +143,14 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie }, expand_all: function(){ var self = this; - var tr = this.$element.find(".oe-treeview-table tbody tr[id^='treerow_']"); + var tr = this.$el.find(".oe-treeview-table tbody tr[id^='treerow_']"); _.each(tr,function(rec){ self.showcontent($(rec).attr('data-id'),true); }); }, collpase_all: function(){ var self = this; - var root_tr = this.$element.find(".oe-treeview-table tbody tr[data-level='"+1+"']"); + var root_tr = this.$el.find(".oe-treeview-table tbody tr[data-level='"+1+"']"); _.each(root_tr,function(rec){ if($(rec).hasClass('oe_open')){ self.showcontent($(rec).attr('data-id'),false); @@ -187,12 +187,12 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie */ hook_row_click: function () { var self = this; - this.$element.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) { + this.$el.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) { e.stopImmediatePropagation(); self.activate($(this).closest('tr').data('id')); }); - this.$element.delegate('.treeview-tr', 'click', function () { + this.$el.delegate('.treeview-tr', 'click', function () { var $this = $(this), record_id = $this.data('id'), bool = $this.parent().hasClass('oe_open'); @@ -220,7 +220,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie render_data: function(groupby){ var self = this; _.each(_.keys(groupby),function(key){ - var $curr_node = self.$element.find('#treerow_' + key); + var $curr_node = self.$el.find('#treerow_' + key); var record = groupby[key]; var children_rows = QWeb.render('TreeView.rows', { 'records': record, @@ -235,7 +235,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie $curr_node.addClass('oe_open'); $curr_node.after(children_rows); } else { - self.$element.find('tbody').html(children_rows); + self.$el.find('tbody').html(children_rows); } }); self.collpase_all(); @@ -272,11 +272,11 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie // show & hide the contents showcontent: function (record_id, show) { - this.$element.find('#treerow_' + record_id) + this.$el.find('#treerow_' + record_id) .toggleClass('oe_open', show); _(this.records[record_id][this.children_field]).each(function (child_id) { - var $child_row = this.$element.find('#treerow_' + child_id); + var $child_row = this.$el.find('#treerow_' + child_id); if ($child_row.hasClass('oe_open')) { this.showcontent(child_id, false); } diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 64817d53bc5..99f4d3e35e8 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -14,10 +14,13 @@ instance.web.ActionManager = instance.web.Widget.extend({ this.dialog = null; this.dialog_widget = null; this.breadcrumbs = []; + this.on('history_back', this, function() { + return this.history_back(); + }); }, start: function() { this._super.apply(this, arguments); - this.$element.on('click', '.oe_breadcrumb_item', this.on_breadcrumb_clicked); + this.$el.on('click', 'a.oe_breadcrumb_item', this.on_breadcrumb_clicked); }, dialog_stop: function () { if (this.dialog) { @@ -50,10 +53,10 @@ instance.web.ActionManager = instance.web.Widget.extend({ } var item = _.extend({ show: function(index) { - this.widget.$element.show(); + this.widget.$el.show(); }, hide: function() { - this.widget.$element.hide(); + this.widget.$el.hide(); }, destroy: function() { this.widget.destroy(); @@ -65,25 +68,58 @@ instance.web.ActionManager = instance.web.Widget.extend({ item.id = _.uniqueId('breadcrumb_'); this.breadcrumbs.push(item); }, + history_back: function() { + var last = this.breadcrumbs.slice(-1)[0]; + if (!last) { + return false; + } + var title = last.get_title(); + if (_.isArray(title) && title.length > 1) { + return this.select_breadcrumb(this.breadcrumbs.length - 1, title.length - 2); + } else if (this.breadcrumbs.length === 1) { + // Only one single titled item in breadcrumb, most of the time you want to trigger back to home + return false; + } else { + var prev = this.breadcrumbs[this.breadcrumbs.length - 2]; + title = prev.get_title(); + return this.select_breadcrumb(this.breadcrumbs.length - 2, _.isArray(title) ? title.length - 1 : undefined); + } + }, on_breadcrumb_clicked: function(ev) { var $e = $(ev.target); var id = $e.data('id'); - var item; + var index; for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { - var it = this.breadcrumbs[i]; - if (it.id == id) { - item = it; + if (this.breadcrumbs[i].id == id) { + index = i; break; } - this.remove_breadcrumb(i); } - var index = $e.parent().find('.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e); - item.show(index, $e); + var subindex = $e.parent().find('a.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e); + this.select_breadcrumb(index, subindex); + }, + select_breadcrumb: function(index, subindex) { + var next_item = this.breadcrumbs[index + 1]; + if (next_item && next_item.on_reverse_breadcrumb) { + next_item.on_reverse_breadcrumb(this.breadcrumbs[index].widget); + } + for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { + if (i > index) { + if (this.remove_breadcrumb(i) === false) { + return false; + } + } + } + var item = this.breadcrumbs[index]; + item.show(subindex); this.inner_widget = item.widget; + return true; }, clear_breadcrumbs: function() { - while (this.breadcrumbs.length) { - this.remove_breadcrumb(0); + for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { + if (this.remove_breadcrumb(0) === false) { + break; + } } }, remove_breadcrumb: function(index) { @@ -93,9 +129,17 @@ instance.web.ActionManager = instance.web.Widget.extend({ return item.widget === it.widget; }); if (!dups.length) { - item.destroy(); + if (this.getParent().has_uncommitted_changes()) { + this.inner_widget = item.widget; + this.breadcrumbs.splice(index, 0, item); + return false; + } else { + item.destroy(); + } } } + var last_widget = this.breadcrumbs.slice(-1)[0]; + this.inner_widget = last_widget && last_widget.widget; }, get_title: function() { var titles = []; @@ -108,7 +152,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ for (var j = 0; j < tit.length; j += 1) { var label = _.escape(tit[j]); if (i === this.breadcrumbs.length - 1 && j === tit.length - 1) { - titles.push(label); + titles.push(_.str.sprintf('<span class="oe_breadcrumb_item">%s</span>', label)); } else { titles.push(_.str.sprintf('<a href="#" class="oe_breadcrumb_item" data-id="%s">%s</a>', item.id, label)); } @@ -117,6 +161,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ return titles.join(' <span class="oe_fade">/</span> '); }, do_push_state: function(state) { + state = state || {}; if (this.getParent() && this.getParent().do_push_state) { if (this.inner_action) { state['title'] = this.inner_action.name; @@ -124,23 +169,34 @@ instance.web.ActionManager = instance.web.Widget.extend({ state['model'] = this.inner_action.res_model; } if (this.inner_action.id) { - state['action_id'] = this.inner_action.id; + state['action'] = this.inner_action.id; + } else if (this.inner_action.type == 'ir.actions.client') { + state['action'] = this.inner_action.tag; + //state = _.extend(this.inner_action.params || {}, state); } } - this.getParent().do_push_state(state); + if(!this.dialog) { + this.getParent().do_push_state(state); + } } }, do_load_state: function(state, warm) { var self = this, action_loaded; - if (state.action_id) { - var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action_id; - if (run_action) { + if (state.action) { + if (_.isString(state.action) && instance.web.client_actions.contains(state.action)) { + var action_client = {type: "ir.actions.client", tag: state.action, params: state}; this.null_action(); - action_loaded = this.do_action(state.action_id); - instance.webclient.menu.has_been_loaded.then(function() { - instance.webclient.menu.open_action(state.action_id); - }); + action_loaded = this.do_action(action_client); + } else { + var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action; + if (run_action) { + this.null_action(); + action_loaded = this.do_action(state.action); + instance.webclient.menu.has_been_loaded.then(function() { + instance.webclient.menu.open_action(state.action); + }); + } } } else if (state.model && state.id) { // TODO handle context & domain ? @@ -154,24 +210,12 @@ instance.web.ActionManager = instance.web.Widget.extend({ action_loaded = this.do_action(action); } else if (state.sa) { // load session action - var self = this; this.null_action(); action_loaded = this.rpc('/web/session/get_session_action', {key: state.sa}).pipe(function(action) { if (action) { return self.do_action(action); } }); - } else if (state.client_action) { - this.null_action(); - var action = state.client_action; - if(_.isString(action)) { - action = { - type: 'ir.actions.client', - tag: action, - params: state, - }; - } - this.ir_actions_client(action); } $.when(action_loaded || null).then(function() { @@ -180,23 +224,23 @@ instance.web.ActionManager = instance.web.Widget.extend({ } }); }, - do_action: function(action, on_close) { + do_action: function(action, on_close, clear_breadcrumbs, on_reverse_breadcrumb) { if (_.isString(action) && instance.web.client_actions.contains(action)) { var action_client = { type: "ir.actions.client", tag: action }; - return this.do_action(action_client); + return this.do_action(action_client, on_close, clear_breadcrumbs, on_reverse_breadcrumb); } else if (_.isNumber(action) || _.isString(action)) { var self = this; - return self.rpc("/web/action/load", { action_id: action }, function(result) { - self.do_action(result.result, on_close); + return self.rpc("/web/action/load", { action_id: action }).pipe(function(result) { + return self.do_action(result.result, on_close, clear_breadcrumbs, on_reverse_breadcrumb); }); } if (!action.type) { console.error("No type for action", action); - return; + return $.Deferred().reject(); } var type = action.type.replace(/\./g,'_'); var popup = action.target === 'new'; - var inline = action.target === 'inline'; + var inline = action.target === 'inline' || action.target === 'inlineview'; action.flags = _.extend({ views_switcher : !popup && !inline, search_view : !popup && !inline, @@ -207,83 +251,96 @@ instance.web.ActionManager = instance.web.Widget.extend({ }, action.flags || {}); if (!(type in this)) { console.error("Action manager can't handle action of type " + action.type, action); - return; + return $.Deferred().reject(); } - return this[type](action, on_close); + return this[type](action, on_close, clear_breadcrumbs, on_reverse_breadcrumb); }, null_action: function() { this.dialog_stop(); this.clear_breadcrumbs(); }, - - do_ir_actions_common: function(action, on_close) { - var self = this, klass, widget, add_breadcrumb; - if (action.type === 'ir.actions.client') { - var ClientWidget = instance.web.client_actions.get_object(action.tag); - widget = new ClientWidget(this, action.params); - klass = 'oe_act_client'; - add_breadcrumb = function() { - self.push_breadcrumb({ - widget: widget, - title: action.name - }); + /** + * + * @param {Object} executor + * @param {Object} executor.action original action + * @param {Function<instance.web.Widget>} executor.widget function used to fetch the widget instance + * @param {String} executor.klass CSS class to add on the dialog root, if action.target=new + * @param {Function<instance.web.Widget, undefined>} executor.post_process cleanup called after a widget has been added as inner_widget + * @param on_close + * @param clear_breadcrumbs + * @return {*} + */ + ir_actions_common: function(executor, on_close, clear_breadcrumbs) { + if (this.inner_widget && executor.action.target !== 'new') { + if (this.getParent().has_uncommitted_changes()) { + return $.Deferred().reject(); + } else if (clear_breadcrumbs) { + this.clear_breadcrumbs(); } - } else { - widget = new instance.web.ViewManagerAction(this, action); - klass = 'oe_act_window'; - add_breadcrumb = widget.proxy('add_breadcrumb'); } - if (action.target === 'new') { + var widget = executor.widget(); + if (executor.action.target === 'new') { if (this.dialog === null) { // These buttons will be overwrited by <footer> if any this.dialog = new instance.web.Dialog(this, { buttons: { "Close": function() { $(this).dialog("close"); }}, - dialogClass: klass + dialogClass: executor.klass }); if(on_close) this.dialog.on_close.add(on_close); } else { this.dialog_widget.destroy(); } - this.dialog.dialog_title = action.name; + this.dialog.dialog_title = executor.action.name; this.dialog_widget = widget; - this.dialog_widget.appendTo(this.dialog.$element); + var initialized = this.dialog_widget.appendTo(this.dialog.$el); this.dialog.open(); + return initialized; } else { this.dialog_stop(); - if(action.menu_id) { - return this.getParent().do_action(action, function () { - instance.webclient.menu.open_menu(action.menu_id); - }); - } - this.inner_action = action; + this.inner_action = executor.action; this.inner_widget = widget; - add_breadcrumb(); - this.inner_widget.appendTo(this.$element); + executor.post_process(widget); + return this.inner_widget.appendTo(this.$el); } }, + ir_actions_act_window: function (action, on_close, clear_breadcrumbs, on_reverse_breadcrumb) { + var self = this; - ir_actions_act_window: function (action, on_close) { + return this.ir_actions_common({ + widget: function () { return new instance.web.ViewManagerAction(self, action); }, + action: action, + klass: 'oe_act_window', + post_process: function (widget) { widget.add_breadcrumb(on_reverse_breadcrumb); } + }, on_close, clear_breadcrumbs, on_reverse_breadcrumb); + }, + ir_actions_client: function (action, on_close, clear_breadcrumbs, on_reverse_breadcrumb) { var self = this; - if (_(['base.module.upgrade', 'base.setup.installer']) - .contains(action.res_model)) { - var old_close = on_close; - on_close = function () { - instance.webclient.do_reload().then(old_close); - }; + var ClientWidget = instance.web.client_actions.get_object(action.tag); + + if (!(ClientWidget.prototype instanceof instance.web.Widget)) { + var next; + if (next = ClientWidget(this, action.params)) { + return this.do_action(next, on_close, clear_breadcrumbs, on_reverse_breadcrumb); + } + return $.when(); } - if (action.target !== 'new') { - if(action.menu_id) { - this.dialog_stop(); - return this.getParent().do_action(action, function () { - instance.webclient.menu.open_menu(action.menu_id); + + return this.ir_actions_common({ + widget: function () { return new ClientWidget(self, action.params); }, + action: action, + klass: 'oe_act_client', + post_process: function(widget) { + self.push_breadcrumb({ + widget: widget, + title: action.name, + on_reverse_breadcrumb: on_reverse_breadcrumb, }); + if (action.tag !== 'reload') { + self.do_push_state({}); + } } - } - return this.do_ir_actions_common(action, on_close); - }, - ir_actions_client: function (action, on_close) { - return this.do_ir_actions_common(action, on_close); + }, on_close, clear_breadcrumbs, on_reverse_breadcrumb); }, ir_actions_act_window_close: function (action, on_closed) { if (!this.dialog && on_closed) { @@ -291,13 +348,13 @@ instance.web.ActionManager = instance.web.Widget.extend({ } this.dialog_stop(); }, - ir_actions_server: function (action, on_closed) { + ir_actions_server: function (action, on_closed, clear_breadcrumbs, on_reverse_breadcrumb) { var self = this; this.rpc('/web/action/run', { action_id: action.id, context: action.context || {} }).then(function (action) { - self.do_action(action, on_closed) + self.do_action(action, on_closed, clear_breadcrumbs, on_reverse_breadcrumb) }); }, ir_actions_report_xml: function(action, on_closed) { @@ -326,9 +383,6 @@ instance.web.ActionManager = instance.web.Widget.extend({ ir_actions_act_url: function (action) { window.open(action.url, action.target === 'self' ? '_self' : '_blank'); }, - ir_ui_menu: function (action) { - this.getParent().do_action(action); - } }); instance.web.ViewManager = instance.web.Widget.extend({ @@ -362,7 +416,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ start: function() { this._super(); var self = this; - this.$element.find('.oe_view_manager_switch a').click(function() { + this.$el.find('.oe_view_manager_switch a').click(function() { self.on_mode_switch($(this).data('view-type')); }).tipsy(); var views_ids = {}; @@ -371,9 +425,9 @@ instance.web.ViewManager = instance.web.Widget.extend({ deferred : $.Deferred(), controller : null, options : _.extend({ - $buttons : self.$element.find('.oe_view_manager_buttons'), - $sidebar : self.flags.sidebar ? self.$element.find('.oe_view_manager_sidebar') : undefined, - $pager : self.$element.find('.oe_view_manager_pager'), + $buttons : self.$el.find('.oe_view_manager_buttons'), + $sidebar : self.flags.sidebar ? self.$el.find('.oe_view_manager_sidebar') : undefined, + $pager : self.$el.find('.oe_view_manager_pager'), action : self.action, action_views_ids : views_ids }, self.flags, self.flags[view.view_type] || {}, view.options || {}) @@ -381,7 +435,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ views_ids[view.view_type] = view.view_id; }); if (this.flags.views_switcher === false) { - this.$element.find('.oe_view_manager_switch').hide(); + this.$el.find('.oe_view_manager_switch').hide(); } // If no default view defined, switch to the first one in sequence var default_view = this.flags.default_view || this.views_src[0].view_type; @@ -398,8 +452,10 @@ instance.web.ViewManager = instance.web.Widget.extend({ var self = this; var view = this.views[view_type]; var view_promise; - if(!view) + var form = this.views['form']; + if (!view || (form && form.controller && !form.controller.can_be_discarded())) { return $.Deferred().reject(); + } if (!no_store) { this.views_history.push(view_type); @@ -418,9 +474,9 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show'](); } - this.$element + this.$el .find('.oe_view_manager_switch a').parent().removeClass('active'); - this.$element + this.$el .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') .parent().addClass('active'); @@ -428,7 +484,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ _.each(_.keys(self.views), function(view_name) { var controller = self.views[view_name].controller; if (controller) { - var container = self.$element.find(".oe_view_manager_view_" + view_name + ":first"); + var container = self.$el.find(".oe_view_manager_view_" + view_name + ":first"); if (view_name === view_type) { container.show(); controller.do_show(view_options || {}); @@ -437,10 +493,10 @@ instance.web.ViewManager = instance.web.Widget.extend({ controller.do_hide(); } // put the <footer> in the dialog's buttonpane - if (self.$element.parent('.ui-dialog-content') && self.$element.find('footer')) { - self.$element.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide() - self.$element.find('footer').appendTo( - self.$element.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonpane') + if (self.$el.parent('.ui-dialog-content') && self.$el.find('footer')) { + self.$el.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide() + self.$el.find('footer').appendTo( + self.$el.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonpane') ); } } @@ -451,17 +507,19 @@ instance.web.ViewManager = instance.web.Widget.extend({ // Lazy loading of views var self = this; var view = this.views[view_type]; - var controllerclass = this.registry.get_object(view_type); + var viewclass = this.registry.get_object(view_type); var options = _.clone(view.options); - if (view_type === "form" && this.action) { - switch (this.action.target) { - case 'new': - case 'inline': - options.initial_mode = 'edit'; - break; - } + if (view_type === "form" && this.action && (this.action.target == 'new' || this.action.target == 'inline')) { + options.initial_mode = 'edit'; } - var controller = new controllerclass(this, this.dataset, view.view_id, options); + var controller = new viewclass(this, this.dataset, view.view_id, options); + + controller.on('history_back', this, function() { + var am = self.getParent(); + if (am && am.trigger) { + return am.trigger('history_back'); + } + }); controller.on("change:title", this, function() { if (self.active_view === view_type) { @@ -475,7 +533,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ controller.do_switch_view.add_last(_.bind(this.switch_view, this)); controller.do_prev_view.add_last(this.on_prev_view); - var container = this.$element.find(".oe_view_manager_view_" + view_type); + var container = this.$el.find(".oe_view_manager_view_" + view_type); var view_promise = controller.appendTo(container); this.views[view_type].controller = controller; this.views[view_type].deferred.resolve(view_type); @@ -489,9 +547,9 @@ instance.web.ViewManager = instance.web.Widget.extend({ }); }, set_title: function(title) { - this.$element.find('.oe_view_title_text:first').text(title); + this.$el.find('.oe_view_title_text:first').text(title); }, - add_breadcrumb: function() { + add_breadcrumb: function(on_reverse_breadcrumb) { var self = this; var views = [this.active_view || this.views_src[0].view_type]; this.on_mode_switch.add(function(mode) { @@ -506,9 +564,9 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.getParent().push_breadcrumb({ widget: this, action: this.action, - show: function(index, $e) { + show: function(index) { var view_to_select = views[index]; - self.$element.show(); + self.$el.show(); if (self.active_view !== view_to_select) { self.on_mode_switch(view_to_select); } @@ -529,13 +587,15 @@ instance.web.ViewManager = instance.web.Widget.extend({ } return controller.get('title'); }); - if (next && next.action && next.action.res_id && self.active_view === 'form' && self.model === next.action.res_model && id === next.action.res_id) { + if (next && next.action && next.action.res_id && self.dataset && + self.active_view === 'form' && self.dataset.model === next.action.res_model && id === next.action.res_id) { // If the current active view is a formview and the next item in the breadcrumbs // is an action on same object (model / res_id), then we omit the current formview's title titles.pop(); } return titles; - } + }, + on_reverse_breadcrumb: on_reverse_breadcrumb, }); }, /** @@ -584,7 +644,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, this.flags.search_view === false); this.searchview.on_search.add(this.do_searchview_search); - return this.searchview.appendTo(this.$element.find(".oe_view_manager_view_search")); + return this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search")); }, do_searchview_search: function(domains, contexts, groupbys) { var self = this, @@ -701,8 +761,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ var manager_ready = $.when(searchview_loaded, main_view_loaded); - this.$element.find('.oe_debug_view').change(this.on_debug_changed); - this.$element.addClass("oe_view_manager_" + (this.action.target || 'current')); + this.$el.find('.oe_debug_view').change(this.on_debug_changed); + this.$el.addClass("oe_view_manager_" + (this.action.target || 'current')); return manager_ready; }, on_debug_changed: function (evt) { @@ -714,7 +774,15 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ switch (val) { case 'fvg': var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open(); - $('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element); + $('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el); + break; + case 'tests': + this.do_action({ + name: "JS Tests", + target: 'new', + type : 'ir.actions.act_url', + url: '/web/static/test/test.html' + }) break; case 'perm_read': var ids = current_view.get_selected_ids(); @@ -733,6 +801,17 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ case 'toggle_layout_outline': current_view.rendering_engine.toggle_layout_debugging(); break; + case 'translate': + this.do_action({ + name: "Technical Translation", + res_model : 'ir.translation', + domain : [['type', '!=', 'object'], '|', ['name', '=', this.dataset.model], ['name', 'ilike', this.dataset.model + ',']], + views: [[false, 'list'], [false, 'form']], + type : 'ir.actions.act_window', + view_type : "list", + view_mode : "list" + }); + break; case 'fields': this.dataset.call_and_eval( 'fields_get', [false, {}], null, 1).then(function (fields) { @@ -780,6 +859,21 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ } }); break; + case 'print_workflow': + if (current_view.get_selected_ids && current_view.get_selected_ids().length == 1) { + instance.web.blockUI(); + var action = { + context: { active_ids: current_view.get_selected_ids() }, + report_name: "workflow.instance.graph", + datas: { + model: this.dataset.model, + id: current_view.get_selected_ids()[0], + nested: true, + } + }; + this.session.get_file({ url: '/web/report', data: {action: JSON.stringify(action)}, complete: instance.web.unblockUI }); + } + break; default: if (val) { console.log("No debug handler for ", val); @@ -812,7 +906,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ var controller = self.views[self.active_view].controller, fvg = controller.fields_view, view_id = (fvg && fvg.view_id) || '--'; - self.$element.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { + self.$el.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { view: controller, view_manager: self })); @@ -826,7 +920,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ return r; }, set_title: function(title) { - this.$element.find('.oe_breadcrumb_title:first').html(this.getParent().get_title()); + this.$el.find('.oe_breadcrumb_title:first').html(this.getParent().get_title()); }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { @@ -858,18 +952,12 @@ instance.web.Sidebar = instance.web.Widget.extend({ var view = this.getParent(); this.sections = [ { 'name' : 'print', 'label' : _t('Print'), }, - { 'name' : 'files', 'label' : _t('Attachment'), }, { 'name' : 'other', 'label' : _t('More'), } ]; this.items = { 'print' : [], - 'files' : [], 'other' : [] }; - if (this.session.uid === 1) { - var item = { label: _t("Translate"), callback: view.on_sidebar_translate, title: _t("Technical translation") }; - this.items.other.push(item); - } this.fileupload_id = _.uniqueId('oe_fileupload'); $(window).on(this.fileupload_id, function() { var args = [].slice.call(arguments).slice(1); @@ -885,7 +973,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ var self = this; this._super(this); this.redraw(); - this.$element.on('click','.oe_dropdown_menu li a', function(event) { + this.$el.on('click','.oe_dropdown_menu li a', function(event) { var section = $(this).data('section'); var index = $(this).data('index'); var item = self.items[section][index]; @@ -901,7 +989,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ }, redraw: function() { var self = this; - self.$element.html(QWeb.render('Sidebar', {widget: self})); + self.$el.html(QWeb.render('Sidebar', {widget: self})); // Hides Sidebar sections when item list is empty this.$('.oe_form_dropdown_section').each(function() { @@ -1008,12 +1096,12 @@ instance.web.Sidebar = instance.web.Widget.extend({ self.items['files'] = attachments; self.redraw(); this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed); - this.$element.find('.oe_sidebar_delete_item').click(this.on_attachment_delete); + this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete); }, on_attachment_changed: function(e) { var $e = $(e.target); if ($e.val() !== '') { - this.$element.find('form.oe_form_binary_form').submit(); + this.$el.find('form.oe_form_binary_form').submit(); $e.parent().find('input[type=file]').prop('disabled', true); $e.parent().find('button').prop('disabled', true).find('img, span').toggle(); this.$('.oe_sidebar_add_attachment span').text(_t('Uploading...')); @@ -1034,126 +1122,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ } }); -instance.web.TranslateDialog = instance.web.Dialog.extend({ - dialog_title: {toString: function () { return _t("Translations"); }}, - init: function(view) { - // TODO fme: should add the language to fields_view_get because between the fields view get - // and the moment the user opens the translation dialog, the user language could have been changed - this.view_language = view.session.user_context.lang; - this['on_button_' + _t("Save")] = this.on_btn_save; - this['on_button_' + _t("Close")] = this.on_btn_close; - this._super(view, { - width: '80%', - height: '80%' - }); - this.view = view; - this.view_type = view.fields_view.type || ''; - this.$fields_form = null; - this.$view_form = null; - this.$sidebar_form = null; - this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name }); - this.languages = null; - this.languages_loaded = $.Deferred(); - (new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(), - [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }).then(this.on_languages_loaded); - }, - start: function() { - var self = this; - this._super(); - $.when(this.languages_loaded).then(function() { - self.$element.html(instance.web.qweb.render('TranslateDialog', { widget: self })); - self.$fields_form = self.$element.find('.oe_translation_form'); - self.$fields_form.find('.oe_trad_field').change(function() { - $(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value'))); - }); - }); - return this; - }, - on_languages_loaded: function(langs) { - this.languages = langs; - this.languages_loaded.resolve(); - }, - do_load_fields_values: function(callback) { - var self = this, - deffered = []; - this.$fields_form.find('.oe_trad_field').val('').removeClass('touched'); - _.each(self.languages, function(lg) { - var deff = $.Deferred(); - deffered.push(deff); - var callback = function(values) { - _.each(self.translatable_fields_keys, function(f) { - self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || '').attr('data-value', values[0][f] || ''); - }); - deff.resolve(); - }; - if (lg.code === self.view_language) { - var values = {}; - _.each(self.translatable_fields_keys, function(field) { - values[field] = self.view.fields[field].get_value(); - }); - callback([values]); - } else { - self.rpc('/web/dataset/get', { - model: self.view.dataset.model, - ids: [self.view.datarecord.id], - fields: self.translatable_fields_keys, - context: self.view.dataset.get_context({ - 'lang': lg.code - })}, callback); - } - }); - $.when.apply(null, deffered).then(callback); - }, - open: function(field) { - var self = this, - sup = this._super; - $.when(this.languages_loaded).then(function() { - if (self.view.translatable_fields && self.view.translatable_fields.length) { - self.do_load_fields_values(function() { - sup.call(self); - // desactivated because it created an exception, plus it does not seem very useful - /* - if (field) { - var $field_input = self.$element.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child'); - self.$element.scrollTo($field_input); - $field_input.focus(); - }*/ - }); - } else { - sup.call(self); - } - }); - }, - on_btn_save: function() { - var trads = {}, - self = this, - trads_mutex = new $.Mutex(); - self.$fields_form.find('.oe_trad_field.touched').each(function() { - var field = $(this).attr('name').split('-'); - if (!trads[field[0]]) { - trads[field[0]] = {}; - } - trads[field[0]][field[1]] = $(this).val(); - }); - _.each(trads, function(data, code) { - if (code === self.view_language) { - _.each(data, function(value, field) { - self.view.fields[field].set_value(value); - }); - } - trads_mutex.exec(function() { - return self.view.dataset.write(self.view.datarecord.id, data, { context : { 'lang': code } }); - }); - }); - this.close(); - }, - on_btn_close: function() { - this.close(); - } -}); - instance.web.View = instance.web.Widget.extend({ - template: "EmptyComponent", // name displayed in view switchers display_name: '', /** @@ -1204,12 +1173,6 @@ instance.web.View = instance.web.Widget.extend({ action_views_ids: {} }); }, - open_translate_dialog: function(field) { - if (!this.translate_dialog) { - this.translate_dialog = new instance.web.TranslateDialog(this).start(); - } - this.translate_dialog.open(field); - }, /** * Fetches and executes the action identified by ``action_data``. * @@ -1233,7 +1196,7 @@ instance.web.View = instance.web.Widget.extend({ var context = new instance.web.CompoundContext(dataset.get_context(), action_data.context || {}); var handler = function (r) { - var action = r.result; + var action = r; if (action && action.constructor == Object) { var ncontext = new instance.web.CompoundContext(context); if (record_id) { @@ -1274,11 +1237,11 @@ instance.web.View = instance.web.Widget.extend({ } } args.push(context); - return dataset.call_button(action_data.name, args, handler); + return dataset.call_button(action_data.name, args).then(handler); } else if (action_data.type=="action") { return this.rpc('/web/action/load', { action_id: action_data.name, context: context, do_not_eval: true}, handler); } else { - return dataset.exec_workflow(record_id, action_data.name, handler); + return dataset.exec_workflow(record_id, action_data.name).then(handler); } }, /** @@ -1292,10 +1255,10 @@ instance.web.View = instance.web.Widget.extend({ this.embedded_view = embedded_view; }, do_show: function () { - this.$element.show(); + this.$el.show(); }, do_hide: function () { - this.$element.hide(); + this.$el.hide(); }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { @@ -1322,23 +1285,8 @@ instance.web.View = instance.web.Widget.extend({ }, do_search: function(view) { }, - on_sidebar_import: function() { - var import_view = new instance.web.DataImport(this, this.dataset); - import_view.start(); - }, on_sidebar_export: function() { - var export_view = new instance.web.DataExport(this, this.dataset); - export_view.start(); - }, - on_sidebar_translate: function() { - return this.do_action({ - res_model : 'ir.translation', - domain : [['type', '!=', 'object'], '|', ['name', '=', this.dataset.model], ['name', 'ilike', this.dataset.model + ',']], - views: [[false, 'list'], [false, 'form']], - type : 'ir.actions.act_window', - view_type : "list", - view_mode : "list" - }); + new instance.web.DataExport(this, this.dataset).open(); }, sidebar_context: function () { return $.when(); @@ -1349,6 +1297,15 @@ instance.web.View = instance.web.Widget.extend({ */ reload: function () { return $.when(); + }, + /** + * Return whether the user can perform the action ('create', 'edit', 'delete') in this view. + * An action is disabled by setting the corresponding attribute in the view's main element, + * like: <form string="" create="false" edit="false" delete="false"> + */ + is_action_enabled: function(action) { + var attrs = this.fields_view.arch.attrs; + return (action in attrs) ? JSON.parse(attrs[action]) : true; } }); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index c45b2d7f226..bf55db8d7c2 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -25,6 +25,13 @@ </div> </div> </t> +<t t-name="Tipsy.alert"> + <a class="oe_tooltip_close oe_e">[</a> + <span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert ui-state-error"></span> + <div class="oe_tooltip_message"> + <t t-esc="message"/> + </div> +</t> <t t-name="CrashManager.warning"> <table cellspacing="0" cellpadding="0" border="0" class="oe_dialog_warning"> @@ -44,29 +51,6 @@ </table> </t> <t t-name="CrashManager.error"> - <t t-if="!session.openerp_entreprise"> - <span>Your version of OpenERP is unsupported. Support & maintenance services are available here: <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">OpenERP Entreprise</a>.</span> - </t> - <t t-if="session.openerp_entreprise"> - <div class="oe_error_send"> - <div> - <div class="oe_centeralign"><b>OpenERP Enterprise Contract.</b></div> - <div><br/>Your report will be sent to the OpenERP Enterprise team.<br/></div><br/> - <div> - <label>Summary:</label><br/> - <input id="issuename" type="text" class="oe_fielddiv"/> - </div><br/><br/> - <div> - <label>Description:</label><br/> - <textarea id="explanation" rows="6"></textarea> - </div><br/><br/> - <div> - <label>What you did:</label><br/> - <textarea id="remark" rows="6" ></textarea> - </div> - </div> - </div><br/> - </t> <div class="oe_error_detail"> <pre><t t-esc="error.message"/></pre> <hr/> @@ -77,7 +61,7 @@ <t t-name="Login"> <div class="oe_login"> <div class="oe_login_bottom"> </div> - <div class="oe_login_error_message">Invalid username or password</div> + <div class="oe_login_error_message"/> <div class="oe_login_pane"> <div class="oe_login_logo"><img src='/web/static/src/img/logo2.png'/></div> <form action="" method="post"> @@ -95,7 +79,7 @@ </form> <div class="oe_login_footer"> <a href="#" class="oe_login_manage_db">Manage Databases</a> | - <a href="http://www.openerp.com">Powered by <span>OpenERP</span></a> + <a href="http://www.openerp.com" target="_blank">Powered by <span>OpenERP</span></a> </div> </div> </div> @@ -114,68 +98,80 @@ </select> </t> + <t t-name="DatabaseManager"> - <div class="oe_database_manager"> - <div class="oe_database_manager_menu"> - <ul class="oe_notebook"> - <li><a href="#db_create">Create</a></li> - <li><a href="#db_drop">Drop</a></li> - <li><a href="#db_backup">Backup</a></li> - <li><a href="#db_restore">Restore</a></li> - <li><a href="#db_change_password">Password</a></li> - <li><a id="back_to_login" href="#">Back to Login</a></li> - </ul> - <form id="db_create" name="create_db_form" method="POST"> + <div class="oe_view_manager_view_form"> + <div class="oe_form"> + <form id="db_create" name="create_db_form" style="display: block;"> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Create Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_create">Create</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> CREATE DATABASE </th> - </tr> - <tr> <td><label for="super_admin_pwd">Master password:</label></td> - <td><input type="password" name="super_admin_pwd" class="required" value="admin"/></td> + <td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td> </tr> <tr> <td><label for="db_name">New database name:</label></td> - <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_]+$" autofocus="true"/></td> + <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" autofocus="true"/></td> </tr> <tr> <td><label for="demo_data">Load Demonstration data:</label></td> - <td><input type="checkbox" name="demo_data"/></td> + <td class="oe_form_group_cell"> + <span class="oe_form_field oe_form_field_boolean"> + <input type="checkbox" name="demo_data" /> + </span> + </td> </tr> <tr> <td><label for="db_lang">Default language:</label></td> - <td> + <td class="oe_form_field oe_form_field_selection "> <select name="db_lang" t-if="widget.lang_list"> <t t-foreach="widget.lang_list" t-as="lang"> - <option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"><t t-esc="lang[1]"/></option> + <option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"> + <t t-esc="lang[1]" /> + </option> </t> </select> </td> </tr> <tr> <td><label for="create_admin_pwd">Admin password:</label></td> - <td><input type="password" name="create_admin_pwd" class="required"/></td> + <td><input type="password" name="create_admin_pwd" class="required" /></td> </tr> <tr> <td><label for="create_confirm_pwd">Confirm password:</label></td> <td><input type="password" name="create_confirm_pwd" class="required" equalTo="input[name=create_admin_pwd]"/></td> </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Create</button></td> - </tr> </table> </form> - <form id="db_drop" name="drop_db_form" method="POST"> + <form id="db_drop" name="drop_db_form" style="display: none; "> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Drop Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_drop">Drop</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> DROP DATABASE </th> - </tr> - <tr> <td><label for="drop_db">Database:</label></td> - <td> + <td class="oe_form_field oe_form_field_selection"> <select t-if="widget.db_list" name="drop_db" autofocus="autofocus"> <t t-foreach="widget.db_list" t-as="db"> - <option t-att-value="db"><t t-esc="db"/></option> + <option t-att-value="db"> + <t t-esc="db" /> + </option> </t> </select> <input t-if="!widget.db_list" name="drop_db" class="required" type="text" autofocus="autofocus"/> @@ -183,25 +179,31 @@ </tr> <tr> <td><label for="drop_password">Master Password:</label></td> - <td><input type="password" name="drop_pwd" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Drop</button></td> + <td><input type="password" name="drop_pwd" class="required" /></td> </tr> </table> </form> - <form id="db_backup" name="backup_db_form" method="POST" target="backup-target" action="/web/database/backup"> - <input type="hidden" name="token"/> + <form id="db_backup" name="backup_db_form" target="backup-target" action="/web/database/backup" style="display: none;"> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Backup Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_backup">Backup</button> + </div> + </div> + </div> + <input type="hidden" name="token" /> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> BACKUP DATABASE </th> - </tr> - <tr> <td><label for="backup_db">Database:</label></td> - <td> + <td class="oe_form_field oe_form_field_selection "> <select t-if="widget.db_list" name="backup_db" autofocus="autofocus"> <t t-foreach="widget.db_list" t-as="db"> - <option t-att-value="db"><t t-esc="db"/></option> + <option t-att-value="db"> + <t t-esc="db" /> + </option> </t> </select> <input t-if="!widget.db_list" name="backup_db" class="required" type="text" autofocus="autofocus"/> @@ -209,19 +211,23 @@ </tr> <tr> <td><label for="backup_pwd">Master Password:</label></td> - <td><input type="password" name="backup_pwd" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Backup</button></td> + <td><input type="password" name="backup_pwd" class="required" /></td> </tr> </table> </form> - <form id="db_restore" name="restore_db_form" method="POST"> + <form id="db_restore" name="restore_db_form" style="display: none; "> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Restore Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_restore">Restore</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> RESTORE DATABASE </th> - </tr> - <tr> <td><label for="restore_db">File:</label></td> <td><input type="file" name="db_file" class="required" autofocus="autofocus"/></td> </tr> @@ -233,17 +239,21 @@ <td><label for="new_db">New database name:</label></td> <td><input type="text" name="new_db" class="required"/></td> </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Restore</button></td> - </tr> </table> </form> - <form id="db_change_password" name="change_pwd_form" method="POST"> + <form id="db_change_password" name="change_pwd_form" style="display: none;"> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Change Master Password</span> + </h2> + <button type="submit" class="oe_button oe_highlight db-change-password">Change Password</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> CHANGE MASTER PASSWORD </th> - </tr> - <tr> <td><label for="old_pwd">Master password:</label></td> <td><input type="password" name="old_pwd" class="required" minlength="1" autofocus="autofocus"/></td> </tr> @@ -253,21 +263,58 @@ </tr> <tr> <td><label for="confirm_pwd">Confirm new master password:</label></td> - <td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + <td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/> </td> </tr> </table> </form> </div> </div> </t> +<t t-name="DatabaseManager.user_menu"> + <span class="oe_right"> + <a id="back-to-login" href="#"><span class="oe_topbar_item oe_topbar_name">Back to Login</span></a> + </span> +</t> +<t t-name="DatabaseManager.menu"> + <div class="oe_secondary_menu_section">Database Management</div> + <ul class="oe_secondary_submenu"> + <li><a href="#db_create">Create</a></li> + <li><a href="#db_drop">Drop</a></li> + <li><a href="#db_backup">Backup</a></li> + <li><a href="#db_restore">Restore</a></li> + <li><a href="#db_change_password">Password</a></li> + </ul> +</t> + +<t t-name="ChangePassword"> + <form name="change_password_form" method="POST"> + <table align="center"> + <tr> + <td><label for="old_pwd">Old Password:</label></td> + <td><input type="password" name="old_pwd" + minlength="1" autofocus="autofocus"/></td> + </tr> + <tr> + <td><label for="new_password">New Password:</label></td> + <td><input type="password" name="new_password" + minlength="1"/></td> + </tr> + <tr> + <td><label for="confirm_pwd">Confirm Password:</label></td> + <td><input type="password" name="confirm_pwd" + minlength="1"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + </tr> + </table> + </form> +</t> <t t-name="Menu"> <ul class="oe_menu" t-if="widget.data"> <li t-foreach="widget.data.data.children" t-as="menu"> - <t t-call="Menu.secondary.link"/> + <t t-call="Menu.link"/> </li> </ul> </t> @@ -286,7 +333,7 @@ <t t-esc="menu.name"/> <!-- Shall the section be still clickable ? - <t t-call="Menu.secondary.link"/> + <t t-call="Menu.link"/> --> </div> <t t-call="Menu.secondary.submenu"/> @@ -296,21 +343,21 @@ <t t-name="Menu.secondary.submenu"> <ul t-if="menu.children.length" class="oe_secondary_submenu"> <li t-foreach="menu.children" t-as="menu"> - <t t-call="Menu.secondary.link"/> + <t t-call="Menu.link"/> <!--<span class="oe_menu_label">8</span>--> <t t-call="Menu.secondary.submenu"/> </li> </ul> </t> -<t t-name="Menu.secondary.link"> - <a t-attf-href="#menu_id=#{menu.id}&action_id=#{menu.action ? menu.action.split(',')[1] : ''}" +<t t-name="Menu.link"> + <a t-attf-href="#menu_id=#{menu.id}&action=#{menu.action ? menu.action.split(',')[1] : ''}" t-att-class="menu.children.length ? 'oe_menu_toggler' : 'oe_menu_leaf'" t-att-data-menu="menu.id" t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''" t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''"> <t t-esc="menu.name"/> <t t-if="menu.needaction_enabled and menu.needaction_counter"> - <div class="oe_menu_counter"> + <div class="oe_tag oe_tag_dark oe_menu_counter"> <t t-esc="menu.needaction_counter"/> </div> </t> @@ -331,7 +378,7 @@ <t t-name="UserMenu.about"> <div class="oe_about"> - <a class="oe_activate_debug_mode oe_right" href="?debug">Activate the developer mode</a> + <a class="oe_activate_debug_mode oe_right" href="?debug" style="background-color: white; padding:2px 6px; border-radius: 10px;">Activate the developer mode</a> <img class="oe_logo" src="/web/static/src/img/logo2.png"/> <h3>Version <t t-esc="version_info.version"/></h3> @@ -344,30 +391,6 @@ </div> </t> -<t t-name="UserMenu.password"> - <form name="change_password_form" method="POST"> - <table align="center"> - <tr> - <td><label for="old_pwd">Old Password:</label></td> - <td><input type="password" name="old_pwd" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="new_password">New Password:</label></td> - <td><input type="password" name="new_password" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="confirm_pwd">Confirm Password:</label></td> - <td><input type="password" name="confirm_pwd" - minlength="1"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> - </tr> - </table> - </form> -</t> <t t-name="WebClient"> <div class="openerp openerp_webclient_container"> @@ -381,13 +404,10 @@ </tr> <tr> <td class="oe_leftbar" valign="top"> - <t t-js="d"> - d.url = '/' + (window.location.search || ''); - </t> - <a t-att-href="url" class="oe_logo"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a> + <a class="oe_logo" href="#"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a> <div class="oe_secondary_menus_container"/> <div class="oe_footer"> - Powered by <a href="http://www.openerp.com"><span>Open</span>ERP</a> + Powered by <a href="http://www.openerp.com" target="_blank"><span>OpenERP</span></a> </div> </td> <td class="oe_application"> @@ -407,9 +427,9 @@ <div class="oe_view_manager"> <table class="oe_view_manager_header"> <col width="20%"/> - <col width="25%"/> - <col width="20%"/> <col width="35%"/> + <col width="15%"/> + <col width="30%"/> <tr class="oe_header_row oe_header_row_top"> <td colspan="2"> <h2 class="oe_view_title" t-if="widget.flags.display_title !== false"> @@ -456,15 +476,18 @@ <option value="">Debug View#<t t-esc="view.fields_view.view_id"/></option> <option t-if="view_manager.active_view === 'form'" value="perm_read" data-views="form,page">View Log (perm_read)</option> <option t-if="view_manager.active_view === 'form'" value="toggle_layout_outline">Toggle Form Layout Outline</option> + <option value="tests">JS Tests</option> <option value="fields">View Fields</option> <option value="fvg">Fields View Get</option> <option value="manage_filters">Manage Filters</option> <t t-if="view_manager.session.uid === 1"> + <option value="translate">Technical translation</option> <option value="manage_views">Manage Views</option> <option value="edit" data-model="ir.ui.view" t-att-data-id="view.fields_view.view_id">Edit <t t-esc="_.str.capitalize(view.fields_view.type)"/>View</option> <option t-if="view_manager.searchview" value="edit" data-model="ir.ui.view" t-att-data-id="view_manager.searchview.view_id">Edit SearchView</option> <option t-if="view_manager.action" value="edit" t-att-data-model="view_manager.action.type" t-att-data-id="view_manager.action.id">Edit Action</option> <option value="edit_workflow">Edit Workflow</option> + <option value="print_workflow">Print Workflow</option> </t> </t> <t t-name="ViewManagerDebugViewLog"> @@ -517,6 +540,7 @@ <t t-foreach="widget.sections" t-as="section"> <div class="oe_form_dropdown_section"> <button class="oe_dropdown_toggle oe_dropdown_arrow"> + <t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/> <t t-esc="section.label"/> </button> <ul class="oe_dropdown_menu"> @@ -553,7 +577,7 @@ <tr> <th t-foreach="fields_view" t-as="field" t-if="!field.attrs.modifiers.tree_invisible"> - <t t-esc="fields[field.attrs.name].string" /> + <t t-esc="field_value.attrs.string || fields[field.attrs.name].string" /> </th> </tr> </thead> @@ -568,7 +592,7 @@ <t t-set="children" t-value="record[children_field]"/> <t t-set="class" t-value="children and children.length ? 'treeview-tr' : 'treeview-td'"/> <t t-set="rank" t-value="'oe-treeview-first'"/> - <t t-set="style" t-value="'background-position: ' + 19*(level-1) + 'px; padding-left: ' + 19*(level-1) + 'px;'"/> + <t t-set="style" t-value="'background-position: ' + 19*(level-1) + 'px; padding-left: ' + (4 + 19*(level-1)) + 'px;'"/> <td t-foreach="fields_view" t-as="field" t-if="!field.attrs.modifiers.tree_invisible" @@ -606,12 +630,12 @@ <input type="checkbox" class="oe_list_record_selector"/> </th> <t t-foreach="columns" t-as="column"> - <th t-if="!column.meta and column.invisible !== '1'" t-att-data-id="column.id" - t-att-class="((options.sortable and column.tag !== 'button') ? 'oe_sortable' : null)"> + <th t-if="!column.meta and column.invisible !== '1' and !column.reorderable" t-att-data-id="column.id" + t-attf-class="oe_list_header_#{column.widget or column.type} #{((options.sortable and column.tag !== 'button') ? 'oe_sortable' : null)}"> <t t-if="column.tag !== 'button'"><t t-esc="column.string"/></t> </th> </t> - <th t-if="options.deletable" width="1"/> + <th t-if="options.deletable" class="oe_list_record_delete" width="13px"/> </tr> </thead> <tfoot> @@ -625,13 +649,10 @@ </tfoot> </table> <div t-name="ListView.buttons" class="oe_list_buttons"> - <t t-if="!widget.no_leaf and widget.options.action_buttons !== false and widget.options.addable"> + <t t-if="!widget.no_leaf and widget.options.action_buttons !== false and widget.options.addable and widget.is_action_enabled('create')"> <button type="button" class="oe_button oe_list_add oe_highlight"> <t t-esc="widget.options.addable"/> </button> - <span class="oe_alternative" t-if="widget.options.import_enabled"> - <span class="oe_fade">or</span> <a href="#" class="oe_bold oe_list_button_import">Import</a> - </span> </t> </div> <t t-name="ListView.pager"> @@ -662,12 +683,12 @@ <t t-foreach="columns" t-as="column"> <t t-set="number" t-value="column.type === 'integer' or column.type == 'float'"/> <t t-set="modifiers" t-value="column.modifiers_for(asData)"/> - <td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help" - t-attf-class="oe_list_field_cell oe_list_field_#{column.widget or column.type} #{number ? 'oe_number' : ''} #{column.tag === 'button' ? 'oe-button' : ''} #{modifiers.readonly ? 'oe_readonly' : ''}" + <td t-if="!column.meta and column.invisible !== '1' and !column.reorderable" t-att-title="column.help" + t-attf-class="oe_list_field_cell oe_list_field_#{column.widget or column.type} #{number ? 'oe_number' : ''} #{column.tag === 'button' ? 'oe-button' : ''} #{modifiers.readonly ? 'oe_readonly' : ''} #{modifiers.required ? 'oe_required' : ''}" t-att-data-field="column.id" ><t t-raw="render_cell(record, column)"/></td> </t> - <td t-if="options.deletable" class='oe_list_record_delete' width="1"> + <td t-if="options.deletable" class='oe_list_record_delete' width="13px"> <button type="button" name="delete" class="oe_i">d</button> </td> </tr> @@ -675,11 +696,17 @@ <t t-jquery="button.oe_list_add" t-operation="after"> <button class="oe_button oe_list_save oe_highlight" type="button">Save</button> - </t> - <t t-jquery="a.oe_list_button_import" t-operation="after"> - <a href="#" class="oe_bold oe_list_discard">discard</a> + <span class="oe_alternative"> + <span class="oe_fade">or</span> + <a href="#" class="oe_bold oe_list_discard">Discard</a> + </span> </t> </t> +<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 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 @@ -701,18 +728,24 @@ <div t-name="FormView.buttons" class="oe_form_buttons"> <t t-if="widget.options.action_buttons !== false"> <span class="oe_form_buttons_view"> - <button type="button" class="oe_button oe_form_button_edit">Edit</button> - <button type="button" class="oe_button oe_form_button_create">Create</button> + <!-- required for the bounce effect on button --> + <div t-if="widget.is_action_enabled('edit')" style="display: inline-block;"> + <button type="button" class="oe_button oe_form_button_edit" accesskey="E">Edit</button> + </div> + <button t-if="widget.is_action_enabled('create')" + type="button" class="oe_button oe_form_button_create" accesskey="C">Create</button> </span> <span class="oe_form_buttons_edit"> - <button type="button" class="oe_button oe_form_button_save oe_highlight">Save</button> <span class="oe_fade">or</span> <a href="#" class="oe_bold oe_form_button_cancel">Discard</a> + <button type="button" class="oe_button oe_form_button_save oe_highlight" accesskey="S">Save</button> + <span class="oe_fade">or</span> + <a href="#" class="oe_bold oe_form_button_cancel" accesskey="D">Discard</a> </span> </t> </div> <t t-name="FormView.pager"> <div class="oe_form_pager"> <t t-if="widget.options.pager !== false" t-call="ViewPager"> - <span class="oe_pager_index">0</span><span class="oe_pager_separator"> / </span><span class="oe_pager_count">0</span> + <span class="oe_form_pager_state"></span> </t> </div> </t> @@ -791,7 +824,7 @@ <div class="oe_clear"> <ul t-attf-class="oe_notebook #{classnames}"> <li t-foreach="pages" t-as="page" t-att-modifiers="page.modifiers"> - <a t-attf-href="##{page.id}"> + <a t-attf-href="##{page.id}" t-att-accesskey="page.accesskey"> <t t-esc="page.string"/> </a> </li> @@ -889,6 +922,7 @@ t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="widget.node.attrs.placeholder" + t-att-maxlength="widget.field.size" /><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> </t> </span> @@ -903,6 +937,7 @@ t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="widget.node.attrs.placeholder" + t-att-maxlength="widget.field.size" /> </div> </t> @@ -921,9 +956,19 @@ t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''" - ></textarea> - <img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" - t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> + t-att-maxlength="widget.field.size" + ></textarea><img class="oe_field_translate oe_input_icon" + t-if="widget.field.translate and !widget.get('effective_readonly')" + t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0" + /> + </div> +</t> +<t t-name="FieldTextHtml"> + <div t-att-class="'oe_form_field oe_form_field_html' + (widget.get('effective_readonly') ? ' oe_form_embedded_html' : '')" + t-att-style="widget.node.attrs.style"> + <t t-if="! widget.get('effective_readonly')"> + <textarea/> + </t> </div> </t> <t t-name="web.datepicker"> @@ -960,13 +1005,13 @@ <t t-name="FieldMany2One"> <span class="oe_form_field oe_form_field_many2one oe_form_field_with_button" t-att-style="widget.node.attrs.style"> <t t-if="widget.get('effective_readonly')"> - <a t-if="! widget.get_definition_options().no_open" href="#" class="oe_form_uri"/> - <span t-if="widget.get_definition_options().no_open" href="#" class="oe_form_uri"/> + <a t-if="! widget.options.no_open" href="#" class="oe_form_uri"/> + <span t-if="widget.options.no_open" href="#" class="oe_form_uri"/> <span class="oe_form_m2o_follow"/> </t> <t t-if="!widget.get('effective_readonly')"> - <a t-if="! widget.get_definition_options().no_open" href="#" tabindex="-1" - class="oe_m2o_cm_button oe_e oe_right">/</a> + <a t-if="! widget.options.no_open" href="#" tabindex="-1" + class="oe_m2o_cm_button oe_e">/</a> <div> <input type="text" t-att-id="widget.id_for_label" @@ -1026,14 +1071,14 @@ </span> </t> <t t-name="FieldStatus"> - <ul class="oe_form_steps" 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"/> </t> <t t-name="FieldStatus.content"> - <t t-set="size" t-value="widget.to_show.length"/> - <t t-foreach="_.range(size)" t-as="i"> - <li t-att-class="widget.to_show[i][0] === widget.selected_value ? 'oe_form_steps_active' : ''"> - <span><t t-esc="widget.to_show[i][1]"/></span> - <img t-att-src='_s + "/web/static/src/img/form_steps.png"' class="oe_form_steps_arrow" t-if="i < size - 1"/> + <t t-foreach="widget.selection" t-as="i"> + <li t-att-class="i[0] === widget.selected_value ? 'oe_active' : ''" t-att-data-id="i[0]"> + <span class="label"><t t-esc="i[1]"/></span> + <!-- are you mit ? --> + <span class="arrow"><span></span></span> </li> </t> </t> @@ -1042,13 +1087,8 @@ <div class="oe_form_field_image_controls oe_edit_only"> <t t-call="HiddenInputFile"> <t t-set="fileupload_id" t-value="widget.fileupload_id"/> - <button class="oe_button" type="button" title="Set Image"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> - </button> + Edit </t> - <button class="oe_button oe_form_binary_file_clear" type="button" title="Clear"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/> - </button> <div class="oe_form_binary_progress" style="display: none"> <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/> <b>Uploading ...</b> @@ -1082,8 +1122,8 @@ <td> <t t-call="HiddenInputFile"> <t t-set="fileupload_id" t-value="widget.fileupload_id"/> - <t t-set="fileupload_style">width: 83px;</t> - <button class="oe_button oe_field_button" type="button" title="Set Image"> + <t t-set="fileupload_style" t-translation="off">width: 83px;</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> </button> @@ -1135,7 +1175,8 @@ <button type="button" class="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-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> </button> @@ -1172,28 +1213,6 @@ </t> </t> -<t t-name="TranslateDialog"> - <table t-if="widget.view.translatable_fields" class="oe_form oe_translation_form" border="0" cellpadding="0" cellspacing="0" width="100%"> - <tr> - <td class="oe_form_separator" width="1%" nowrap="nowrap"> - <div class="oe_horizontal_separator">Field</div> - </td> - <th t-foreach="widget.languages" align="left"> - <div class="oe_horizontal_separator"><t t-esc="name"/></div> - </th> - </tr> - <tr t-foreach="widget.view.translatable_fields" t-as="field" t-att-data-field="field.name"> - <td class="oe_form_group_cell" width="1%" nowrap="nowrap"> - <label class="oe_label"><t t-esc="field.node.attrs.string"/>:</label> - </td> - <td t-foreach="widget.languages" t-as="lg" class="oe_form_group_cell"> - <input t-if="field.type == 'char'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field" style="width: 100%"/> - <textarea t-if="field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" style="width: 100%"></textarea> - </td> - </tr> - </table> -</t> - <t t-name="AbstractFormPopup.render"> <div> <table style="width:100%"> @@ -1219,14 +1238,24 @@ <a class="oe_button oe_selectcreatepopup-search-close oe_bold oe_form_button_cancel" href="javascript:void(0)">Cancel</a> </t> <t t-name="AbstractFormPopup.buttons"> - <t t-if="! multi_select"> - <button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save</button> - </t> - <t t-if="multi_select"> - <button type="button" class="oe_button oe_abstractformpopup-form-save-new oe_highlight">Save & New</button> - <button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save & Close</button> + <t t-if="! readonly"> + <t t-if="! multi_select"> + <button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save</button> + </t> + <t t-if="multi_select"> + <button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save & Close</button> + <button type="button" class="oe_button oe_abstractformpopup-form-save-new oe_highlight">Save & New</button> + </t> + or </t> - or <a class="oe_button oe_abstractformpopup-form-close oe_bold oe_form_button_cancel" href="javascript:void(0)">Discard</a> + <a class="oe_button oe_abstractformpopup-form-close oe_bold oe_form_button_cancel" href="javascript:void(0)"> + <t t-if="! readonly"> + Discard + </t> + <t t-if="readonly"> + Close + </t> + </a> </t> <t t-name="One2Many.viewmanager" t-extend="ViewManager"> @@ -1268,7 +1297,7 @@ contenteditable="true"/> <!-- tabindex: makes div focusable --> <div t-name="SearchView.FacetView" - class="oe_searchview_facet" + class="oe_tag oe_tag_dark oe_searchview_facet" tabindex="0" ><span class="oe_facet_remove">x</span ><span class="oe_facet_category oe_i" t-if="widget.model.has('icon')"> @@ -1443,20 +1472,7 @@ <div> </div> </div> -<div t-name="SearchView.addtoreporting" class="oe_searchview_dashboard"> - <h4>Add to Dashboard</h4> - <form> - <p><input placeholder="Title of new dashboard item"/></p> - <button class="oe_apply" type="submit">Add</button> - </form> -</div> -<t t-name="SearchView.addtoreporting.selection"> - <select> - <option t-foreach="selections" t-as="element" - t-att-value="element.id || element.res_id "> - <t t-esc="element.name"/></option> - </select> -</t> + <div t-name="SearchView.advanced" class="oe_searchview_advanced"> <h4>Advanced Search</h4> <form> @@ -1469,6 +1485,7 @@ </div> <t t-name="SearchView.extended_search.proposition"> <li> + <span class="searchview_extended_prop_or">or</span> <select class="searchview_extended_prop_field"> <t t-foreach="widget.attrs.fields" t-as="field"> <option t-att="{'selected': field === widget.attrs.selected ? 'selected' : null}" @@ -1682,5 +1699,22 @@ <div t-name="Many2ManyKanban.quick_create" class="oe_kanban_quick_create"> <input t-att-placeholder="_t('Type name to search')"/> </div> - +<t t-name="Throbber"> + <div> + <div class="oe_blockui_spin" style="height: 50px"> + </div> + <br /> + <div class="oe_throbber_message" style="color:white"></div> + </div> +</t> +<t t-name="M2ODialog"> + <div> + Name: <input class="oe_form_m2o_input_name" type="text"/> + </div> +</t> +<t t-name="M2ODialog.buttons"> + <button class="oe_form_m2o_qc_button oe_button oe_highlight">Quick Add</button> + <button class="oe_form_m2o_sc_button oe_button">Add All Info...</button> + <button class="oe_form_m2o_cancel_button oe_button">Cancel</button> +</t> </templates> |
