From 582ccb5ab9061bcc3cd4d1511ca6c5c1d20a6adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 20 Aug 2014 17:02:16 +0200 Subject: [PATCH 01/29] [IMP] web client refactoring: action and view manager large rewrite of action manager, view manager and viewmanageraction. The goal is to lay the foundation for the redesign of the advanced search view. This commit includes: * improvements to the display order (no/less flickering) * large simplification of the breadcrumbs code * much better html layout for the header (no table) * bootstrapisation of the view manager * lots of optimizations/simplification * a base.css.map, courtesy of the last sass version --- addons/web/static/src/css/base.css | 343 ++++----- addons/web/static/src/css/base.sass | 131 ++-- addons/web/static/src/js/search.js | 6 +- addons/web/static/src/js/views.js | 1037 +++++++++------------------ addons/web/static/src/xml/base.xml | 119 ++- 5 files changed, 649 insertions(+), 987 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 23fb4ed2f09..41d1f35bd0c 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -161,16 +161,16 @@ } .openerp .zebra tbody tr:hover td { background-color: #e6e6e6; - 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); + background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#dedede)); + background-image: -webkit-linear-gradient(top, #eee, #dedede); + background-image: -moz-linear-gradient(top, #eee, #dedede); + background-image: -ms-linear-gradient(top, #eee, #dedede); + background-image: -o-linear-gradient(top, #eee, #dedede); + background-image: linear-gradient(to bottom, #eee, #dedede); } .openerp input, .openerp textarea, .openerp select { padding: 2px 4px; - border: 1px solid #cccccc; + border: 1px solid #ccc; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; @@ -184,7 +184,7 @@ font-weight: bold; font-size: inherit; } -.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp .oe_button, .openerp input[type='submit'] { +.openerp a.button:link, .openerp a.button:visited, .openerp .oe_button, .openerp input[type='submit'] { display: inline-block; border: 1px solid rgba(0, 0, 0, 0.4); color: #4c4c4c; @@ -209,7 +209,7 @@ -webkit-font-smoothing: antialiased; outline: none; } -.openerp a.button:hover, .openerp button:hover, .openerp .oe_button:hover, .openerp input[type='submit']:hover { +.openerp a.button:hover, .openerp .oe_button:hover, .openerp input[type='submit']:hover { background-color: #ececec; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -220,7 +220,7 @@ cursor: pointer; background-position: 0; } -.openerp a.button:focus, .openerp button:focus, .openerp .oe_button:focus, .openerp input[type='submit']:focus { +.openerp a.button:focus, .openerp .oe_button:focus, .openerp input[type='submit']:focus { border: 1px solid #80bfff; background-position: 0; background-color: #ececec; @@ -234,7 +234,7 @@ -webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; } -.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp .oe_button:active, .openerp .oe_button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active { +.openerp a.button:active, .openerp a.button.active, .openerp .oe_button:active, .openerp .oe_button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active { background-color: #ececec; background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)); background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6); @@ -246,18 +246,18 @@ -webkit-box-shadow: none; box-shadow: none; } -.openerp a.button.disabled, .openerp button:disabled, .openerp .oe_button:disabled, .openerp input[type='submit']:disabled { +.openerp a.button.disabled, .openerp .oe_button:disabled, .openerp input[type='submit']:disabled { background: #efefef !important; border: 1px solid #d1d1d1 !important; -moz-box-shadow: none !important; -webkit-box-shadow: none !important; box-shadow: none !important; - color: #aaaaaa !important; + color: #aaa !important; cursor: default; - text-shadow: 0 1px 1px white !important; + text-shadow: 0 1px 1px #fff !important; } .openerp .ui-widget-content a { - color: #7c7bad; + color: #7C7BAD; } .openerp .oe_bounce_container { display: inline-block; @@ -305,7 +305,7 @@ cursor: wait; } .openerp .oe_fade { - color: #888888; + color: #888; font-weight: normal; } .openerp .oe_bold { @@ -316,7 +316,7 @@ } .openerp .oe_highlight { color: white; - background: #dc5f59; + background: #DC5F59; } .openerp button.oe_highlight { background-color: #c02c2c; @@ -349,7 +349,7 @@ background-image: linear-gradient(to bottom, #c52020, #d22323); } .openerp .oe_background_grey { - background: #eeeeee !important; + background: #eee !important; } .openerp .oe_form_dirty .oe_highlight_on_dirty { color: white; @@ -369,7 +369,7 @@ box-shadow: none; } .openerp .oe_form_dirty button.oe_highlight_on_dirty:hover { - background: #ed6f6a; + background: #ED6F6A; } .openerp .oe_warning_redirect { border: none !important; @@ -385,7 +385,7 @@ font-weight: normal; width: 132px !important; height: 40px; - color: #666666; + color: #666; margin: 0px -1px -1px 0px; padding: 0; border: 1px solid #dddddd; @@ -401,7 +401,7 @@ line-height: 120%; } .openerp .oe_stat_button .stat_button_icon { - color: #7c7bad; + color: #7C7BAD; font-size: 24px; padding: 0px 3px; width: 37px; @@ -482,7 +482,7 @@ -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; - color: #7c7bad; + color: #7C7BAD; font-weight: bold; } .openerp .oe_button.oe_link span:hover { @@ -505,7 +505,7 @@ max-width: 650px; } .openerp .oe_grey { - color: #aaaaaa; + color: #aaa; } .openerp .oe_error_detail hr { display: block; @@ -525,10 +525,10 @@ -webkit-border-radius: 6px; border-radius: 6px; background-position: center center; - background-image: -webkit-radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%); - background-image: -moz-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%); - background-image: -ms-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%); - background-image: radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%); + background-image: -webkit-radial-gradient(circle, #eee 0%, #ccc 40%, #bbb 100%); + background-image: -moz-radial-gradient(#eee 0%, #ccc 40%, #bbb 100%); + background-image: -ms-radial-gradient(#eee 0%, #ccc 40%, #bbb 100%); + background-image: radial-gradient(circle, #eee 0%, #ccc 40%, #bbb 100%); } .openerp .oe_kanban_status_green { background: green; @@ -562,11 +562,11 @@ -webkit-border-radius: 3px; border-radius: 3px; background: #f0f0fa; - color: #4c4c4c; + color: #4C4C4C; } .openerp .oe_tag_dark { - background: #7c7bad; - color: #eeeeee; + background: #7C7BAD; + color: #eee; } .openerp .oe_form_field_radio.oe_horizontal { white-space: nowrap; @@ -636,13 +636,13 @@ } .openerp .oe_notebook > li > a { display: block; - color: gray; + color: #808080; } .openerp .oe_notebook > li.ui-tabs-active > a { color: #4c4c4c; } .openerp .oe_notebook { - border-color: #dddddd; + border-color: #ddd; border-style: solid; border-width: 0 0 1px; } @@ -660,16 +660,16 @@ } .openerp .oe_notebook > li > a:hover { text-decoration: none; - background-color: #eeeeee; - border-color: #eeeeee #eeeeee #dddddd; + background-color: #eee; + border-color: #eee #eee #ddd; } .openerp .ui-tabs .oe_notebook.ui-tabs-nav li.ui-tabs-active { border-bottom: none; padding-bottom: 1px; } .openerp .oe_notebook > li.ui-state-active > a, .openerp .oe_notebook > li.ui-state-active > a:hover { - background-color: white; - border: 1px solid #dddddd; + background-color: #ffffff; + border: 1px solid #ddd; border-bottom-color: transparent; cursor: default; } @@ -690,7 +690,7 @@ cursor: pointer; } .openerp .oe_dropdown_toggle { - color: #2b2b2b; + color: #2B2B2B; font-weight: normal; } .openerp .oe_dropdown_hover:hover .oe_dropdown_menu, .openerp .oe_dropdown_menu.oe_opened { @@ -812,10 +812,10 @@ top: 0; right: 50%; padding: 4px 12px; - background: #a61300; + background: #A61300; color: white; text-align: center; - border: 1px solid #990000; + border: 1px solid #900; border-top: none; -moz-border-radius-bottomright: 8px; -moz-border-radius-bottomleft: 8px; @@ -833,12 +833,12 @@ width: 32px; } .openerp .oe_database_manager { - background: white; - color: black; + background: #fff; + color: #000; text-align: left; } .openerp .oe_database_manager .oe_database_manager_menu { - color: black; + color: #000; } .openerp .oe_webclient { width: 100%; @@ -881,7 +881,7 @@ width: 100%; display: none; text-align: center; - color: #eeeeee; + color: #eee; background: rgba(37, 37, 37, 0.9); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -923,7 +923,7 @@ .openerp .oe_secondary_menu_section { font-weight: bold; margin-left: 8px; - color: #7c7bad; + color: #7C7BAD; } .openerp .oe_secondary_submenu { margin-bottom: 10px !important; @@ -1003,7 +1003,7 @@ } .openerp .oe_about .user-details .user-info-block .navigation li.active a { background: #a24689; - color: white; + color: #fff; } .openerp .oe_about .user-details .user-info-block .user-body { text-align: center; @@ -1027,10 +1027,10 @@ transform: scale(1.3); } .openerp .oe_about #social .social-fb:hover { - color: #3b5998; + color: #3B5998; } .openerp .oe_about #social .social-tw:hover { - color: #4099ff; + color: #4099FF; } .openerp .oe_about #social .social-gp:hover { color: #d34836; @@ -1045,8 +1045,11 @@ width: 100%; height: 100%; } -.openerp .oe_application a { - color: #7c7bad; +.openerp .oe_application .oe-view-manager-body a { + color: #7C7BAD; +} +.openerp .oe_application .oe-view-manager-body .oe-view-manager-view-kanban:not(:empty) { + height: 100%; } .openerp .oe_application > div { position: relative; @@ -1079,6 +1082,63 @@ overflow: hidden; font-weight: bold; } +.openerp .oe-view-manager-header { + height: 90px; + overflow: hidden; + border-bottom: 1px solid black; + display: none; +} +.openerp .oe-view-manager-header .row { + height: 45px; +} +.openerp .oe-view-manager-header .oe-view-title { + float: left; + font-size: 18px; + font-weight: bold; + margin: 0; +} +.openerp .oe-view-manager-header .oe-header-title { + padding-left: 0; +} +.openerp .oe-view-manager-header .oe-view-manager-search-view { + float: right; + padding-top: 5px; +} +.openerp .oe-view-manager-header .oe-view-manager-switch { + float: right; +} +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-kanban:before { + content: ""; +} +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-list:before { + content: ""; +} +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-form:before { + content: ""; +} +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-graph:before { + content: ""; +} +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-calendar:before { + content: ""; +} +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-gantt:before { + content: ""; +} +.openerp .oe-view-manager-header .oe-view-manager-buttons { + float: left; +} +.openerp .oe-view-manager-header .oe-view-manager-buttons > div { + display: none; +} +.openerp .oe_application > div > div > .oe-view-manager-body { + position: absolute; + top: 90px; + bottom: 0; + left: 0; + right: 0; + overflow: auto; +} .openerp .oe_view_manager { display: table; height: inherit; @@ -1122,7 +1182,7 @@ line-height: 30px; } .openerp .oe_view_manager table.oe_view_manager_header h2 a { - color: #7c7bad; + color: #7C7BAD; } .openerp .oe_view_manager table.oe_view_manager_header .oe_dropdown_menu { line-height: normal; @@ -1148,13 +1208,13 @@ text-decoration: none; } .openerp .oe_view_manager table.oe_view_manager_header .oe_button_group .active { - background: #999999; + background: #999; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; } .openerp .oe_view_manager table.oe_view_manager_header .oe_button_group .active a { - color: white; + color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_buttons { @@ -1250,40 +1310,18 @@ text-decoration: none; } .openerp .oe_pager_group .active { - background: #999999; + background: #999; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; } .openerp .oe_pager_group .active a { - color: white; + color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .openerp .oe_list_pager.oe_list_pager_single_page .oe_pager_group { display: none; } -.openerp .oe_view_manager_current { - height: 100%; -} -.openerp .oe_view_manager_current > .oe_view_manager_header { - border-bottom: 1px solid #cacaca; - background-color: #ededed; - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); - background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); - background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); - background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); - background-image: -o-linear-gradient(top, #fcfcfc, #dedede); - background-image: linear-gradient(to bottom, #fcfcfc, #dedede); - -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); - -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); -} -.openerp .oe_view_manager_current > .oe_view_manager_header .oe_header_row td { - padding: 8px; -} -.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_inlineview { height: 100%; } @@ -1305,18 +1343,12 @@ .openerp .oe_searchview { cursor: text; position: relative; - float: right; padding: 1px 0; - line-height: 18px; min-width: 400px; - border: 1px solid #ababab; - background: white; + width: 400px; -moz-border-radius: 13px; -webkit-border-radius: 13px; border-radius: 13px; - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; } .openerp .oe_searchview input, .openerp .oe_searchview textarea { padding: 3px; @@ -1343,73 +1375,44 @@ .openerp .oe_searchview .oe_searchview_clear { cursor: pointer; position: absolute; - top: 0; + top: 3px; right: 18px; width: 15px; height: 24px; - background: url(../img/search_reset.gif) center center no-repeat; } .openerp .oe_searchview .oe_searchview_unfold_drawer { position: absolute; top: 0; right: 0; - height: 24px; - padding: 0 7px 0 4px; - color: #cccccc; + color: #ccc; cursor: pointer; } .openerp .oe_searchview .oe_searchview_unfold_drawer:hover { - color: #999999; + color: #999; } .openerp .oe_searchview .oe_searchview_unfold_drawer:before { position: absolute; - top: 10px; - right: 7px; + top: 7px; + right: 18px; width: 0; - height: 0; - display: inline-block; - content: ""; - vertical-align: top; - border-top: 5px solid #4c4c4c; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - filter: alpha(opacity=50); - opacity: 0.5; } .openerp .oe_searchview .oe_searchview_search { - font-size: 1px; - letter-spacing: -1px; - color: transparent; - text-shadow: none; - font-weight: normal; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; + cursor: pointer; position: absolute; - left: 3px; - top: 1px; - padding: 0; - border: none; - background: transparent; -} -.openerp .oe_searchview .oe_searchview_search:before { - font: 21px "mnmliconsRegular"; - content: "r"; - color: #a3a3a3; + left: 6px; + top: 8px; } .openerp .oe_searchview .oe_searchview_facets { min-height: 22px; - margin: 0 35px 0 15px; + margin: 2px 35px 0 20px; } .openerp .oe_searchview .oe_searchview_facets * { vertical-align: top; display: inline-block; - line-height: 17px; + font-size: 100%; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { + height: 20px; margin: 1px 0; font-size: 11px; } @@ -1431,6 +1434,9 @@ padding: 0; -webkit-font-smoothing: auto; } +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet span { + height: 18px; +} .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { border-color: #a6a6fe; -moz-box-shadow: 0 0 3px 1px #a6a6fe; @@ -1439,9 +1445,6 @@ } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_values { background: #f0f0fa; - -moz-border-radius: 0 3px 3px 0; - -webkit-border-radius: 0 3px 3px 0; - border-radius: 0 3px 3px 0; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_category, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value { padding: 0 4px; @@ -1454,9 +1457,7 @@ font-size: 16px; } .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; + color: #4C4C4C; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:last-child { padding-right: 16px; @@ -1560,7 +1561,7 @@ margin-bottom: 0; } .openerp .oe_searchview_drawer dt { - color: #7c7bad; + color: #7C7BAD; font-size: 13px; line-height: 24px; } @@ -1783,8 +1784,8 @@ display: inline-block; } .openerp .oe_form_invalid input, .openerp .oe_form_invalid select, .openerp .oe_form_invalid textarea { - background-color: #ff6666 !important; - border: 1px solid #dd0000 !important; + background-color: #F66 !important; + border: 1px solid #D00 !important; } .openerp .oe_view_manager_current .oe_form_editable .oe_highlight { color: #404040; @@ -1878,7 +1879,7 @@ } .openerp .oe_application .oe_form_sheetbg { background: url(/web/static/src/img/form_sheetbg.png); - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid #ddd; } .openerp .oe_application .oe_form_sheetbg .oe_subtotal_footer label { font-weight: bold; @@ -1938,7 +1939,7 @@ padding: 16px 16px 48px; } .openerp .oe_form div.oe_form_configuration p, .openerp .oe_form div.oe_form_configuration ul, .openerp .oe_form div.oe_form_configuration ol { - color: #aaaaaa; + color: #aaa; max-width: 650px; } .openerp .oe_form div.oe_form_configuration label { @@ -1979,8 +1980,8 @@ font-weight: normal !important; } .openerp .oe_form .oe_form_box_info { - background: #ffee99; - border-bottom: 1px solid #ccbb66; + background: #fe9; + border-bottom: 1px solid #cb6; padding: 4px; } .openerp .oe_form .oe_form_box_info > p { @@ -1988,7 +1989,7 @@ } .openerp .oe_form .oe_form_box_warning { background: #bd362f; - border-bottom: 1px solid #990000; + border-bottom: 1px solid #900; padding: 4px; } .openerp .oe_form .oe_form_box_warning * { @@ -2002,7 +2003,7 @@ margin: 2px; } .openerp .oe_form td.oe_form_group_cell_label { - border-right: 1px solid #dddddd; + border-right: 1px solid #ddd; padding: 2px 0px; } .openerp .oe_form td.oe_form_group_cell_label label { @@ -2027,7 +2028,7 @@ } .openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span { font-size: 80%; - color: darkgreen; + color: darkGreen; vertical-align: top; position: relative; top: -4px; @@ -2040,13 +2041,13 @@ font-weight: bold; font-size: 20px; margin: 15px 0px 10px 0px; - color: #7c7bad; + color: #7C7BAD; } .openerp .oe_horizontal_separator:empty { height: 5px; } .openerp .oe_vertical_separator { - border-left: 1px solid #666666; + border-left: 1px solid #666; padding: 0 4px 0 4px; } .openerp .oe_form_field_progressbar { @@ -2060,7 +2061,7 @@ -moz-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box; - border: 1px solid #999999; + border: 1px solid #999; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; @@ -2141,8 +2142,8 @@ font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; } .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] { - background: #e5e5e5 !important; - color: #666666; + background: #E5E5E5 !important; + color: #666; } .openerp .oe_form textarea[disabled] { border: none; @@ -2156,7 +2157,7 @@ color: #4c4c4c; } .openerp .oe_form textarea.oe_inline[disabled] { - border-left: 8px solid #eeeeee; + border-left: 8px solid #eee; } .openerp .oe_form .oe_form_field_url button img { vertical-align: top; @@ -2173,11 +2174,11 @@ display: inline-block; } .openerp .oe_form .oe_form_required input:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required select:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required textarea:not([disabled]):not([readonly]) { - background-color: #d2d2ff !important; + 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 { - background-color: #ff6666 !important; - border: 1px solid #dd0000 !important; + background-color: #F66 !important; + border: 1px solid #D00 !important; } .openerp .oe_form .oe_input_icon { cursor: pointer; @@ -2267,7 +2268,7 @@ width: 100%; display: none; text-align: center; - color: #eeeeee; + color: #eee; background: rgba(37, 37, 37, 0.9); -moz-border-radius: 3px 3px 0 0; -webkit-border-radius: 3px 3px 0 0; @@ -2304,7 +2305,7 @@ .openerp .oe_fileupload .oe_add button.oe_attach { width: 24px; background: transparent; - color: #7c7bad; + color: #7C7BAD; box-shadow: none; border: none; text-shadow: none; @@ -2323,7 +2324,7 @@ cursor: pointer; } .openerp .oe_fileupload .oe_add .oe_attach_label { - color: #7c7bad; + color: #7C7BAD; margin-left: -3px; } .openerp .oe_fileupload .oe_attachments { @@ -2349,7 +2350,7 @@ background: white; } .openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) { - background: #f4f5fa; + background: #F4F5FA; } .openerp .oe_form_field_many2one { display: inline-block; @@ -2510,7 +2511,7 @@ background-image: linear-gradient(to bottom, #e8e8e8, #cacaca); } .openerp ul.oe_form_status_clickable li > .label { - color: #7c7bad; + color: #7C7BAD; } .openerp ul.oe_form_status_clickable li.oe_active:hover { background-color: #3a699f; @@ -2580,7 +2581,7 @@ background-color: #d2d2ff; } .openerp .oe_form_editable .oe_list_editable .oe_list_content td.oe_readonly { - background-color: #eeeeee; + background-color: #eee; } .openerp .oe_list_editable .oe_list_content td.oe_list_field_cell { padding: 4px 6px 3px; @@ -2617,7 +2618,7 @@ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border: 1px solid #aaaaff; + border: 1px solid #aaf; margin: 0; } .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select { @@ -2709,7 +2710,7 @@ } .openerp .oe_list_content > thead { border-bottom: 2px solid #cacaca; - background: #eeeeee; + background: #eee; vertical-align: top; } .openerp .oe_list_content td, .openerp .oe_list_content th { @@ -2728,7 +2729,7 @@ margin-top: 7px; border-width: 0 4px 4px; border-style: solid; - border-color: black transparent; + border-color: #000 transparent; visibility: hidden; } .openerp .oe_list_content th.sortup div:after { @@ -2745,7 +2746,7 @@ border-bottom: none; border-left: 4px solid transparent; border-right: 4px solid transparent; - border-top: 4px solid black; + border-top: 4px solid #000; visibility: visible; -moz-box-shadow: none; -webkit-box-shadow: none; @@ -2758,7 +2759,7 @@ } .openerp .oe_list_content > tbody > tr { height: 27px; - border-top: 1px solid #dddddd; + border-top: 1px solid #ddd; } .openerp .oe_list_content > tbody > tr > td.oe_list_field_cell { padding: 3px 6px; @@ -2797,7 +2798,7 @@ .openerp .oe_list_content > tfoot { border-top: 2px solid #cacaca; border-bottom: 1px solid #cacaca; - background: #eeeeee; + background: #eee; font-weight: bold; } .openerp .oe_list_content .numeric { @@ -2828,7 +2829,7 @@ .openerp .oe_list_content td.oe_list_field_handle .oe_list_handle:before { font: 18px "entypoRegular"; content: "}"; - color: #e0e0e0; + color: #E0E0E0; } .openerp .oe_list_content .oe_list_field_progressbar progress { width: 100%; @@ -2990,7 +2991,7 @@ -webkit-margin-end: 0px; } .openerp pre { - background-color: white; + background-color: #FFFFFF; border: none; padding: 10px 0 3px 0; } @@ -3035,9 +3036,9 @@ -moz-border-radius: 15px; -webkit-border-radius: 15px; border-radius: 15px; - -moz-box-shadow: 0 0 5px 5px #999999; - -webkit-box-shadow: 0 0 5px 5px #999999; - box-shadow: 0 0 5px 5px #999999; + -moz-box-shadow: 0 0 5px 5px #999; + -webkit-box-shadow: 0 0 5px 5px #999; + box-shadow: 0 0 5px 5px #999; } div.ui-widget-overlay { @@ -3076,7 +3077,7 @@ div.ui-widget-overlay { background: #f0f0fa; } .ui-menu .ui-menu-item a.ui-state-hover, .ui-menu .ui-menu-item a.ui-state-active { - background: #7c7bad; + background: #7C7BAD; } .ui-corner-all { @@ -3090,7 +3091,7 @@ div.ui-widget-overlay { } body.oe_single_form { - background: #eeeeee url(/web/static/src/img/form_sheetbg.png); + background: #eee url(/web/static/src/img/form_sheetbg.png); height: 100%; } body.oe_single_form .oe_single_form_logo { @@ -3124,7 +3125,7 @@ body.oe_single_form .oe_single_form_container { width: 80px; } .openerp_ie .oe_form_field_boolean input { - background: white; + background: #fff; } .openerp_ie .db_option_table .oe_form_field_selection { width: auto; @@ -3315,7 +3316,7 @@ body.oe_single_form .oe_single_form_container { max-width: 350px; } .tooltip .tooltip-inner .oe_tooltip_string { - color: #ffdd55; + color: #FD5; font-weight: bold; font-size: 13px; } @@ -3339,7 +3340,7 @@ body.oe_single_form .oe_single_form_container { color: white; } .tooltip .tooltip-inner .oe_tooltip_close:hover { - color: #999999; + color: #999; cursor: pointer; } .tooltip .tooltip-inner .oe_tooltip_message { @@ -3414,7 +3415,7 @@ div.tour-backdrop { bottom: 0; left: 0; z-index: 1100; - background-color: black; + background-color: #000; opacity: 0.8; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index a8efd0f29cb..5aeaccf07aa 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -267,7 +267,7 @@ $sheet-padding: 16px font-size: inherit // }}} // Button style {{{ - a.button:link, a.button:visited, button, .oe_button, input[type='submit'] + a.button:link, a.button:visited, .oe_button, input[type='submit'] display: inline-block border: 1px solid rgba(0,0,0,0.4) color: #4c4c4c @@ -282,22 +282,22 @@ $sheet-padding: 16px -webkit-font-smoothing: antialiased outline: none - a.button:hover, button:hover,.oe_button:hover, input[type='submit']:hover + a.button:hover,.oe_button:hover, input[type='submit']:hover @include vertical-gradient(#f6f6f6, #e3e3e3) cursor: pointer background-position: 0 - a.button:focus, button:focus, .oe_button:focus, input[type='submit']:focus + a.button:focus, .oe_button:focus, input[type='submit']:focus border: 1px solid #80bfff background-position: 0 @include vertical-gradient(#f6f6f6, #e3e3e3) @include box-shadow((0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, .8) inset)) - a.button:active, a.button.active, button:active, .oe_button:active, .oe_button.active, input[type='submit']:active, input[type='submit'].active + a.button:active, a.button.active, .oe_button:active, .oe_button.active, input[type='submit']:active, input[type='submit'].active @include vertical-gradient(#e3e3e3, #f6f6f6) @include box-shadow(none) - a.button.disabled, button:disabled, .oe_button:disabled, input[type='submit']:disabled + a.button.disabled, .oe_button:disabled, input[type='submit']:disabled background: #efefef !important border: 1px solid #d1d1d1 !important @include box-shadow(none !important) @@ -899,8 +899,11 @@ $sheet-padding: 16px .oe_application width: 100% height: 100% - a - color: $link-color + .oe-view-manager-body + a + color: $link-color + .oe-view-manager-view-kanban:not(:empty) + height: 100% > div position: relative height: 100% @@ -931,6 +934,52 @@ $sheet-padding: 16px font-weight: bold // }}} // ViewManager common {{{ + .oe-view-manager-header + height: 90px + overflow: hidden + border-bottom: 1px solid black + display: none + .row + height: 45px + .oe-view-title + float: left + font-size: 18px + font-weight: bold + margin: 0 + .oe-header-title + padding-left: 0 + .oe-view-manager-search-view + float: right + padding-top: 5px + .oe-view-manager-switch + float: right + .oe-vm-switch-kanban:before + content: "\f009" + .oe-vm-switch-list:before + content: "\f0c9" + .oe-vm-switch-form:before + content: "\f044" + .oe-vm-switch-graph:before + content: "\f080" + .oe-vm-switch-calendar:before + content: "\f073" + .oe-vm-switch-gantt:before + content: "\f0ae" + .oe-view-manager-buttons + float: left + > div + display: none + + .oe_application + > div + > div + > .oe-view-manager-body + position: absolute + top: 90px + bottom: 0 + left: 0 + right: 0 + overflow: auto .oe_view_manager display: table height: inherit @@ -1076,18 +1125,18 @@ $sheet-padding: 16px display: none // }}} // ViewManager application {{{ - .oe_view_manager_current - height: 100% - > .oe_view_manager_header - border-bottom: 1px solid #cacaca - @include vertical-gradient(#fcfcfc, #dedede) - @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) - .oe_header_row - td - padding: 8px - .oe_header_row:first-child - td - padding-top: 8px + // .oe_view_manager_current + // height: 100% + // > .oe_view_manager_header + // border-bottom: 1px solid #cacaca + // @include vertical-gradient(#fcfcfc, #dedede) + // @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) + // .oe_header_row + // td + // padding: 8px + // .oe_header_row:first-child + // td + // padding-top: 8px // }}} // ViewManager inline {{{ .oe_view_manager_inline, .oe_view_manager_inlineview @@ -1111,14 +1160,10 @@ $sheet-padding: 16px .oe_searchview cursor: text position: relative - float: right padding: 1px 0 - line-height: 18px min-width: 400px - border: 1px solid #ababab - background: white + width: 400px @include radius(13px) - @include box-shadow(0 1px 2px rgba(0,0,0,0.2) inset) input, textarea padding: 3px height: 14px @@ -1139,54 +1184,39 @@ $sheet-padding: 16px .oe_searchview_clear cursor: pointer position: absolute - top: 0 + top: 3px right: 18px width: 15px height: 24px - background: url(../img/search_reset.gif) center center no-repeat .oe_searchview_unfold_drawer position: absolute top: 0 right: 0 - height: 24px - padding: 0 7px 0 4px color: #ccc cursor: pointer &:hover color: #999 &:before position: absolute - top: 10px - right: 7px + top: 7px + right: 18px width: 0 - height: 0 - display: inline-block - content: "" - vertical-align: top - border-top: 5px solid #4C4C4C - border-left: 5px solid transparent - border-right: 5px solid transparent - @include opacity() .oe_searchview_search - @include text-to-icon("r", #a3a3a3) - @include box-shadow(none) - @include radius(0) + cursor: pointer position: absolute - left: 3px - top: 1px - padding: 0 - border: none - background: transparent + left: 6px + top: 8px .oe_searchview_facets min-height: 22px - margin: 0 35px 0 15px + margin: 2px 35px 0 20px * vertical-align: top display: inline-block - line-height: 17px + font-size: 100% .oe_searchview_facet + height: 20px margin: 1px 0 font-size: 11px &:focus @@ -1204,12 +1234,13 @@ $sheet-padding: 16px padding: 0 -webkit-font-smoothing: auto // spacing for opera, FF + span + height: 18px &:focus border-color: $tag-border-selected @include box-shadow(0 0 3px 1px $tag-border-selected) .oe_facet_values background: $tag-bg-light - @include radius(0 3px 3px 0) .oe_facet_category, .oe_facet_value padding: 0 4px .oe_facet_category @@ -1218,8 +1249,6 @@ $sheet-padding: 16px .oe_facet_category.oe_i font-size: 16px .oe_facet_value - border-left: 1px solid $tag-border - text-shadow: 0 1px 1px white color: #4C4C4C &:last-child padding-right: 16px diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index f14b8c010f1..e28b1185357 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -330,7 +330,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } }, // search button - 'click button.oe_searchview_search': function (e) { + 'click i.oe_searchview_search': function (e) { e.stopImmediatePropagation(); this.do_search(); }, @@ -450,6 +450,10 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea this.drawer = drawer; }, + toggle_visibility: function (is_visible) { + this.$el.toggle(is_visible); + }, + show: function () { this.$el.show(); }, diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 248436357ad..ac8d146823b 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -3,6 +3,7 @@ *---------------------------------------------------------*/ (function() { +"use strict"; var instance = openerp; openerp.web.views = {}; @@ -14,17 +15,14 @@ instance.web.ActionManager = instance.web.Widget.extend({ this._super(parent); this.inner_action = null; this.inner_widget = null; + this.webclient = parent; this.dialog = null; this.dialog_widget = null; - this.breadcrumbs = []; + this.view_managers = []; this.on('history_back', this, function() { return this.history_back(); }); }, - start: function() { - this._super.apply(this, arguments); - this.$el.on('click', 'a.oe_breadcrumb_item', this.on_breadcrumb_clicked); - }, dialog_stop: function (reason) { if (this.dialog) { this.dialog.destroy(reason); @@ -47,184 +45,131 @@ instance.web.ActionManager = instance.web.Widget.extend({ * @param {String|Array} [item.title] title(s) of the view(s) to be displayed in the breadcrumb * @param {Function} [item.get_title] should return the title(s) of the view(s) to be displayed in the breadcrumb */ - push_breadcrumb: function(item) { - var last = this.breadcrumbs.slice(-1)[0]; - if (last) { - last.hide(); + push_view_manager: function(widget, action, clear_breadcrumbs) { + var self = this, + old_widget = this.inner_widget; + + if (clear_breadcrumbs) { + var to_destroy = this.view_managers; + this.view_managers = []; } - item = _.extend({ - show: function(index) { - this.widget.$el.show(); - }, - hide: function() { - this.widget.$el.hide(); - }, - destroy: function() { - this.widget.destroy(); - }, - get_title: function() { - return this.title || this.widget.get('title'); + this.view_managers.push(widget); + this.inner_action = action; + this.inner_widget = widget; + return $.when(this.inner_widget.appendTo(this.$el)).done(function () { + widget.$header && widget.$header.show(); + old_widget && old_widget.$el.hide(); + if (clear_breadcrumbs) { + self.clear_view_managers(to_destroy) } - }, item); - item.id = _.uniqueId('breadcrumb_'); - this.breadcrumbs.push(item); + }); + }, + get_breadcrumbs: function () { + return _.flatten(_.map(this.view_managers, function (vm) { + return vm.view_stack.map(function (view, index) { + return { + title: view.controller.get('title') || vm.title, + index: index, + view_manager: vm, + }; + }); + }), true); }, 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); - } + // 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 index; - for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { - if (this.breadcrumbs[i].id == id) { - index = i; - break; - } - } - 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; - this.inner_action = item.action; - return true; - }, - clear_breadcrumbs: function() { - for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { - if (this.remove_breadcrumb(0) === false) { - break; - } - } - }, - remove_breadcrumb: function(index) { - var item = this.breadcrumbs.splice(index, 1)[0]; - if (item) { - var dups = _.filter(this.breadcrumbs, function(it) { - return item.widget === it.widget; + select_view_manager: function(view_manager, index) { + var self = this; + var vm_index = this.view_managers.indexOf(view_manager); + view_manager.select_view(index).done(function () { + _.each(self.view_managers.splice(vm_index + 1), function (vm) { + vm.destroy(); }); - if (!dups.length) { - if (this.getParent().has_uncommitted_changes()) { - this.inner_widget = item.widget; - this.inner_action = item.action; - this.breadcrumbs.splice(index, 0, item); - return false; - } else { - item.destroy(); - } - } - } - var last_widget = this.breadcrumbs.slice(-1)[0]; - if (last_widget) { - this.inner_widget = last_widget.widget; - this.inner_action = last_widget.action; + self.inner_widget = _.last(self.view_managers); + self.inner_widget.display_breadcrumbs(); + self.inner_widget.$el.show(); + }); + }, + clear_view_managers: function(vms) { + _.each(vms || this.view_managers, function (vm) { + vm.destroy(); + }); + if (!vms) { + this.view_managers = []; + this.inner_widget = null; } }, add_breadcrumb_url: function (url, label) { // Add a pseudo breadcrumb that will redirect to an url - this.push_breadcrumb({ - show: function() { - instance.web.redirect(url); - }, - hide: function() {}, - destroy: function() {}, - get_title: function() { - return label; - } - }); + // this.push_breadcrumb({ + // show: function() { + // instance.web.redirect(url); + // }, + // hide: function() {}, + // destroy: function() {}, + // get_title: function() { + // return label; + // } + // }); }, - get_title: function() { - var titles = []; - for (var i = 0; i < this.breadcrumbs.length; i += 1) { - var item = this.breadcrumbs[i]; - var tit = item.get_title(); - if (item.hide_breadcrumb) { - continue; + do_push_state: function(state) { + if (!this.webclient || !this.webclient.do_push_state) { + return; + } + state = state || {}; + if (this.inner_action) { + if (this.inner_action._push_me === false) { + // this action has been explicitly marked as not pushable + return; } - if (!_.isArray(tit)) { - tit = [tit]; + state.title = this.inner_action.name; + if(this.inner_action.type == 'ir.actions.act_window') { + state.model = this.inner_action.res_model; } - 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(_.str.sprintf('%s', label)); - } else { - titles.push(_.str.sprintf('%s', item.id, label)); + if (this.inner_action.menu_id) { + state.menu_id = this.inner_action.menu_id; + } + if (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; + var params = {}; + _.each(this.inner_action.params, function(v, k) { + if(_.isString(v) || _.isNumber(v)) { + params[k] = v; + } + }); + state = _.extend(params || {}, state); + } + if (this.inner_action.context) { + var active_id = this.inner_action.context.active_id; + if (active_id) { + state.active_id = active_id; + } + var active_ids = this.inner_action.context.active_ids; + if (active_ids && !(active_ids.length === 1 && active_ids[0] === active_id)) { + // We don't push active_ids if it's a single element array containing the active_id + // This makes the url shorter in most cases. + state.active_ids = this.inner_action.context.active_ids.join(','); } } } - return titles.join(' / '); - }, - do_push_state: function(state) { - state = state || {}; - if (this.getParent() && this.getParent().do_push_state) { - if (this.inner_action) { - if (this.inner_action._push_me === false) { - // this action has been explicitly marked as not pushable - return; - } - state['title'] = this.inner_action.name; - if(this.inner_action.type == 'ir.actions.act_window') { - state['model'] = this.inner_action.res_model; - } - if (this.inner_action.menu_id) { - state['menu_id'] = this.inner_action.menu_id; - } - if (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; - var params = {}; - _.each(this.inner_action.params, function(v, k) { - if(_.isString(v) || _.isNumber(v)) { - params[k] = v; - } - }); - state = _.extend(params || {}, state); - } - if (this.inner_action.context) { - var active_id = this.inner_action.context.active_id; - if (active_id) { - state["active_id"] = active_id; - } - var active_ids = this.inner_action.context.active_ids; - if (active_ids && !(active_ids.length === 1 && active_ids[0] === active_id)) { - // We don't push active_ids if it's a single element array containing the active_id - // This makes the url shorter in most cases. - state["active_ids"] = this.inner_action.context.active_ids.join(','); - } - } - } - if(!this.dialog) { - this.getParent().do_push_state(state); - } + if(!this.dialog) { + this.webclient.do_push_state(state); } }, do_load_state: function(state, warm) { @@ -390,7 +335,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ }, null_action: function() { this.dialog_stop(); - this.clear_breadcrumbs(); + this.clear_view_managers(); }, /** * @@ -403,14 +348,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ * @return {*} */ ir_actions_common: function(executor, options) { - if (this.inner_widget && executor.action.target !== 'new') { - if (this.getParent().has_uncommitted_changes()) { - return $.Deferred().reject(); - } else if (options.clear_breadcrumbs) { - this.clear_breadcrumbs(); - } - } - var widget = executor.widget(); + var widget; if (executor.action.target === 'new') { var pre_dialog = (this.dialog && !this.dialog.isDestroyed()) ? this.dialog : null; if (pre_dialog){ @@ -442,6 +380,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ } }; this.dialog.on("closing", null, this.dialog.on_close); + widget = executor.widget(); if (widget instanceof instance.web.ViewManager) { _.extend(widget.flags, { $buttons: this.dialog.$buttons, @@ -453,30 +392,33 @@ instance.web.ActionManager = instance.web.Widget.extend({ var initialized = this.dialog_widget.appendTo(this.dialog.$el); this.dialog.open(); return initialized; - } else { - // explicitly passing a closing action to dialog_stop() prevents - // it from reloading the original form view - we're opening a - // completely new action anyway - this.dialog_stop(executor.action); - this.inner_action = executor.action; - this.inner_widget = widget; - executor.post_process(widget); - return this.inner_widget.appendTo(this.$el); } + if (this.inner_widget && this.webclient.has_uncommitted_changes()) { + return $.Deferred().reject(); + } + widget = executor.widget(); + this.dialog_stop(executor.action); + if (options.clear_breadcrumbs) { + // this.clear_view_managers(); + } + return this.push_view_manager(widget, executor.action, options.clear_breadcrumbs); }, ir_actions_act_window: function (action, options) { var self = this; return this.ir_actions_common({ - widget: function () { return new instance.web.ViewManagerAction(self, action); }, + 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: options.on_reverse_breadcrumb, - hide_breadcrumb: options.hide_breadcrumb, - }); - }, + // post_process: function (widget) { + // self.push_breadcrumb({ + // widget: widget, + // on_reverse_breadcrumb: options.on_reverse_breadcrumb, + // hide_breadcrumb: options.hide_breadcrumb, + // }); + // }, }, options); }, ir_actions_client: function (action, options) { @@ -498,17 +440,17 @@ instance.web.ActionManager = instance.web.Widget.extend({ widget: function () { return new ClientWidget(self, action); }, action: action, klass: 'oe_act_client', - post_process: function(widget) { - self.push_breadcrumb({ - widget: widget, - title: action.name, - on_reverse_breadcrumb: options.on_reverse_breadcrumb, - hide_breadcrumb: options.hide_breadcrumb, - }); - if (action.tag !== 'reload') { - self.do_push_state({}); - } - } + // post_process: function(widget) { + // self.push_breadcrumb({ + // widget: widget, + // title: action.name, + // on_reverse_breadcrumb: options.on_reverse_breadcrumb, + // hide_breadcrumb: options.hide_breadcrumb, + // }); + // if (action.tag !== 'reload') { + // self.do_push_state({}); + // } + // } }, options); }, ir_actions_act_window_close: function (action, options) { @@ -578,247 +520,186 @@ instance.web.ActionManager = instance.web.Widget.extend({ instance.web.ViewManager = instance.web.Widget.extend({ template: "ViewManager", + /** + * @param {Object} [dataset] null object (... historical reasons) + * @param {Array} [views] List of [view_id, view_type] + * @param {Object} [flags] various boolean describing UI state + */ init: function(parent, dataset, views, flags) { + var self = this; this._super(parent); - this.url_states = {}; - this.model = dataset ? dataset.model : undefined; + + this.flags = flags; this.dataset = dataset; - this.searchview = null; - this.active_view = null; - this.views_src = _.map(views, function(x) { - if (x instanceof Array) { - var view_type = x[1]; - var View = instance.web.views.get_object(view_type, true); - var view_label = View ? View.prototype.display_name : (void 'nope'); - return { - view_id: x[0], - view_type: view_type, - label: view_label, - button_label: View ? _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}) : (void 'nope'), - }; - } else { - return x; - } - }); - this.ActionManager = parent; + this.view_order = []; + this.url_states = {}; this.views = {}; - this.flags = flags || {}; + this.view_stack = []; // used for breadcrumbs + this.active_view = null; + this.searchview = null; + this.active_search = null; this.registry = instance.web.views; - this.views_history = []; - this.view_completely_inited = $.Deferred(); + this.title = this.action && this.action.name; + + _.each(views, function (view) { + var view_type = view[1] || view.view_type, + View = instance.web.views.get_object(view_type, true), + view_label = View ? View.prototype.display_name : (void 'nope'); + self.view_order.push(view_type); + self.views[view_type] = { + controller: null, + options: view.options || {}, + view_id: view[0] || view.view_id, + type: view_type, + label: view_label, + embedded_view: view.embedded_view, + title: self.action && self.action.name, + button_label: View ? _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}) : (void 'nope'), + }; + }); }, /** * @returns {jQuery.Deferred} initial view loading promise */ start: function() { + var self = this; + var default_view = this.flags.default_view || this.view_order[0], + default_options = this.flags[default_view] && this.flags[default_view].options; + this._super(); - var self = this; - this.$el.find('.oe_view_manager_switch a').click(function() { + var $sidebar = this.flags.sidebar ? this.$('.oe_view_manager_sidebar') : undefined, + $pager = this.$('.oe_view_manager_pager'); + + this.$breadcrumbs = this.$('.oe-view-title'); + this.$switch_buttons = this.$('.oe-view-manager-switch a'); + this.$header = this.$('.oe-view-manager-header'); + + self.$switch_buttons.click(function () { self.switch_mode($(this).data('view-type')); - }).tooltip(); - var views_ids = {}; - _.each(this.views_src, function(view) { - self.views[view.view_type] = $.extend({}, view, { - deferred : $.Deferred(), - controller : null, - options : _.extend({ - $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 || {}) - }); - - views_ids[view.view_type] = view.view_id; }); - if (this.flags.views_switcher === false) { - 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; - - return this.switch_mode(default_view, null, this.flags[default_view] && this.flags[default_view].options); - + var views_ids = {}; + _.each(this.views, function (view) { + views_ids[view.type] = view.view_id; + view.options = _.extend({ + $buttons: self.$('.oe-' + view.type + '-buttons'), + $sidebar : $sidebar, + $pager : $pager, + action : self.action, + action_views_ids : views_ids, + }, self.flags, self.flags[view.type], view.options); + }); + var search_view_loaded = this.flags.search_view ? this.setup_search_view() : $.when(), + main_view_loaded = this.switch_mode(default_view, null, default_options); + + return $.when(main_view_loaded, search_view_loaded); }, + switch_mode: function(view_type, no_store, view_options) { - var self = this; - var view = this.views[view_type]; - var view_promise; - 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); - } - this.active_view = view_type; + var self = this, + view = this.views[view_type]; - if (!view.controller) { - view_promise = this.do_create_view(view_type); - } else if (this.searchview - && self.flags.auto_search + if (view_type !== 'form') { + this.view_stack = []; + } + + this.view_stack.push(view); + this.active_view = view; + if (!view.created) { + view.created = this.create_view.bind(this)(view); + } + this.active_search = $.Deferred(); + + if (this.searchview + && this.flags.auto_search && view.controller.searchable !== false) { - this.searchview.ready.done(this.searchview.do_search); + $.when(this.searchview.ready, view.created).done(this.searchview.do_search); + } else { + this.active_search.resolve(); } - if (this.searchview) { - this.searchview[(view.controller.searchable === false || this.searchview.options.hidden) ? 'hide' : 'show'](); - } - - this.$el.find('.oe_view_manager_switch a').parent().removeClass('active'); - this.$el - .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') - .parent().addClass('active'); - this.$el.attr("data-view-type", view_type); - return $.when(view_promise).done(function () { - _.each(_.keys(self.views), function(view_name) { - var controller = self.views[view_name].controller; - if (controller) { - var container = self.$el.find("> div > div > .oe_view_manager_body > .oe_view_manager_view_" + view_name); - if (view_name === view_type) { - container.show(); - controller.do_show(view_options || {}); - } else { - container.hide(); - controller.do_hide(); - } - } - }); + self.update_header(); + return $.when(view.created, this.active_search).done(function () { + self.active_view = view; + self._display_view(view.type, view_options); self.trigger('switch_mode', view_type, no_store, view_options); }); }, - do_create_view: function(view_type) { - // Lazy loading of views + update_header: function () { + this.$switch_buttons.removeClass('active'); + this.$('.oe-vm-switch-' + this.active_view.type).addClass('active'); + }, + _display_view: function (view_type, view_options) { var self = this; - var view = this.views[view_type]; - var viewclass = this.registry.get_object(view_type); - var options = _.clone(view.options); - if (view_type === "form" && this.action && (this.action.target == 'new' || this.action.target == 'inline')) { + this.active_view.$container.show(); + $.when(this.active_view.controller.do_show(view_options)).done(function () { + _.each(self.views, function (view) { + if (view.type !== view_type) { + view.controller && view.controller.do_hide(); + view.$container && view.$container.hide(); + view.options.$buttons && view.options.$buttons.hide(); + } + }); + self.active_view.options.$buttons && self.active_view.options.$buttons.show(); + if (self.searchview) { + var is_hidden = self.active_view.controller.searchable === false || self.searchview.options.hidden; + self.searchview.toggle_visibility(!is_hidden); + } + self.display_breadcrumbs(); + }); + }, + display_breadcrumbs: function () { + var self = this; + if (!this.action_manager) return; + var breadcrumbs = this.action_manager.get_breadcrumbs(); + var $breadcrumbs = _.map(_.initial(breadcrumbs), function (bc) { + var $link = $('').text(bc.title); + $link.click(function () { + self.action_manager.select_view_manager(bc.view_manager, bc.index); + }); + return $('
  • ').append($link); + }); + $breadcrumbs.push($('
  • ').addClass('active').text(_.last(breadcrumbs).title)); + this.$breadcrumbs + .empty() + .append($breadcrumbs); + }, + create_view: function(view) { + var self = this, + View = this.registry.get_object(view.type), + options = _.clone(view.options); + + if (view.type === "form" && this.action && (this.action.target === 'new' || this.action.target === 'inline')) { options.initial_mode = 'edit'; } - var controller = new viewclass(this, this.dataset, view.view_id, options); + var controller = new View(this, this.dataset, view.view_id, options), + $container = this.$(".oe-view-manager-view-" + view.type + ":first"); - 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) { - self.set_title(controller.get('title')); - } - }); + $container.hide(); + view.controller = controller; if (view.embedded_view) { controller.set_embedded_view(view.embedded_view); } - controller.on('switch_mode', self, this.switch_mode); - controller.on('previous_view', self, this.prev_view); + controller.on('switch_mode', this, this.switch_mode.bind(this)); - var container = this.$el.find("> div > div > .oe_view_manager_body > .oe_view_manager_view_" + view_type); - var view_promise = controller.appendTo(container); - this.views[view_type].controller = controller; - return $.when(view_promise).done(function() { - self.views[view_type].deferred.resolve(view_type); - if (self.searchview - && self.flags.auto_search - && view.controller.searchable !== false) { - self.searchview.ready.done(self.searchview.do_search); - } else { - self.view_completely_inited.resolve(); - } - self.trigger("controller_inited",view_type,controller); + view.$container = $container; + + var view_loaded = $.Deferred(); + view.controller.on('view_loaded', this, function () { + view_loaded.resolve(); }); + + return $.when(controller.appendTo($container), view_loaded) + .done(function () { + self.trigger("controller_inited", view.type, controller); + }); }, - /** - * @returns {Number|Boolean} the view id of the given type, false if not found - */ - get_view_id: function(view_type) { - return this.views[view_type] && this.views[view_type].view_id || false; - }, - set_title: function(title) { - this.$el.find('.oe_view_title_text:first').text(title); - }, - add_breadcrumb: function(options) { - options = options || {}; - var self = this; - var views = [this.active_view || this.views_src[0].view_type]; - this.on('switch_mode', self, function(mode) { - var last = views.slice(-1)[0]; - if (mode !== last) { - if (mode !== 'form') { - views.length = 0; - } - views.push(mode); - } - instance.web.bus.trigger('view_switch_mode', self, mode); - }); - var item = _.extend({ - widget: this, - action: this.action, - show: function(index) { - var view_to_select = views[index]; - var state = self.url_states[view_to_select]; - self.do_push_state(state || {}); - $.when(self.switch_mode(view_to_select)).done(function() { - self.$el.show(); - }); - }, - get_title: function() { - var id; - var currentIndex; - _.each(self.getParent().breadcrumbs, function(bc, i) { - if (bc.widget === self) { - currentIndex = i; - } - }); - var next = self.getParent().breadcrumbs.slice(currentIndex + 1)[0]; - var titles = _.map(views, function(v) { - var controller = self.views[v].controller; - if (v === 'form') { - id = controller.datarecord.id; - } - return controller.get('title'); - }); - 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; - } - }, options); - this.getParent().push_breadcrumb(item); - }, - /** - * Returns to the view preceding the caller view in this manager's - * navigation history (the navigation history is appended to via - * switch_mode) - * - * @param {Object} [options] - * @param {Boolean} [options.created=false] resource was created - * @param {String} [options.default=null] view to switch to if no previous view - * @returns {$.Deferred} switching end signal - */ - prev_view: function (options) { - options = options || {}; - var current_view = this.views_history.pop(); - var previous_view = this.views_history[this.views_history.length - 1] || options['default']; - if (options.created && current_view === 'form' && previous_view === 'list') { - // APR special case: "If creation mode from list (and only from a list), - // after saving, go to page view (don't come back in list)" - return this.switch_mode('form'); - } else if (options.created && !previous_view && this.action && this.action.flags.default_view === 'form') { - // APR special case: "If creation from dashboard, we have no previous view - return this.switch_mode('form'); - } - return this.switch_mode(previous_view, true); + select_view: function (index) { + var view_type = this.view_stack[index].type; + this.view_stack.splice(index); + return this.switch_mode(view_type); }, /** * Sets up the current viewmanager's search view. @@ -826,25 +707,37 @@ instance.web.ViewManager = instance.web.Widget.extend({ * @param {Number|false} view_id the view to use or false for a default one * @returns {jQuery.Deferred} search view startup deferred */ - setup_search_view: function(view_id, search_defaults) { - var self = this; + setup_search_view: function() { if (this.searchview) { this.searchview.destroy(); } + var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; + var search_defaults = {}; + + var context = this.action ? this.action.context : []; + _.each(context, function (value, key) { + var match = /^search_default_(.*)$/.exec(key); + if (match) { + search_defaults[match[1]] = value; + } + }); + + var options = { hidden: this.flags.search_view === false, disable_custom_filters: this.flags.search_disable_custom_filters, }; - this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, options); + var SearchView = instance.web.SearchView; + this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options); - this.searchview.on('search_data', self, this.do_searchview_search); - return this.searchview.appendTo(this.$(".oe_view_manager_view_search"), - this.$(".oe_searchview_drawer_container")); + this.searchview.on('search_data', this, this.search.bind(this)); + return this.searchview.appendTo(this.$(".oe-view-manager-search-view:first"), + this.$('.oe_searchview_drawer_container')); }, - do_searchview_search: function(domains, contexts, groupbys) { + search: function(domains, contexts, groupbys) { var self = this, - controller = this.views[this.active_view].controller, + controller = this.active_view.controller, action_context = this.action.context || {}; instance.web.pyeval.eval_domains_and_contexts({ domains: [this.action.domain || []].concat(domains || []), @@ -865,301 +758,48 @@ instance.web.ViewManager = instance.web.Widget.extend({ groupby = [groupby]; } $.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() { - self.view_completely_inited.resolve(); + self.active_search.resolve(); }); }); }, - /** - * Called when one of the view want to execute an action - */ - on_action: function(action) { - }, - on_create: function() { - }, - on_remove: function() { - }, - on_edit: function() { - }, - /** - * Called by children view after executing an action - */ - on_action_executed: function () { - }, -}); - -instance.web.ViewManagerAction = instance.web.ViewManager.extend({ - template:"ViewManagerAction", - /** - * @constructs instance.web.ViewManagerAction - * @extends instance.web.ViewManager - * - * @param {instance.web.ActionManager} parent parent object/widget - * @param {Object} action descriptor for the action this viewmanager needs to manage its views. - */ - init: function(parent, action) { - // dataset initialization will take the session from ``this``, so if we - // do not have it yet (and we don't, because we've not called our own - // ``_super()``) rpc requests will blow up. - var flags = action.flags || {}; - if (!('auto_search' in flags)) { - flags.auto_search = action.auto_search !== false; - } - if (action.res_model == 'board.board' && action.view_mode === 'form') { - // Special case for Dashboards - _.extend(flags, { - views_switcher : false, - display_title : false, - search_view : false, - pager : false, - sidebar : false, - action_buttons : false - }); - } - this._super(parent, null, action.views, flags); - this.session = parent.session; - this.action = action; - var context = action.context; - if (action.target === 'current'){ - var active_context = { - active_model: action.res_model, - }; - context = new instance.web.CompoundContext(context, active_context).eval(); - delete context['active_id']; - delete context['active_ids']; - if (action.res_id){ - context['active_id'] = action.res_id; - context['active_ids'] = [action.res_id]; - } - } - var dataset = new instance.web.DataSetSearch(this, action.res_model, context, action.domain); - if (action.res_id) { - dataset.ids.push(action.res_id); - dataset.index = 0; - } - this.dataset = dataset; - }, - /** - * Initializes the ViewManagerAction: sets up the searchview (if the - * searchview is enabled in the manager's action flags), calls into the - * parent to initialize the primary view and (if the VMA has a searchview) - * launches an initial search after both views are done rendering. - */ - start: function() { - var self = this, - searchview_loaded, - search_defaults = {}; - _.each(this.action.context, function (value, key) { - var match = /^search_default_(.*)$/.exec(key); - if (match) { - search_defaults[match[1]] = value; - } - }); - // init search view - var searchview_id = this.action['search_view_id'] && this.action['search_view_id'][0]; - - searchview_loaded = this.setup_search_view(searchview_id || false, search_defaults); - - var main_view_loaded = this._super(); - - var manager_ready = $.when(searchview_loaded, main_view_loaded, this.view_completely_inited); - - 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) { - var self = this, - $sel = $(evt.currentTarget), - $option = $sel.find('option:selected'), - val = $sel.val(), - current_view = this.views[this.active_view].controller; - switch (val) { - case 'fvg': - var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get") }).open(); - $('
    ').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el);
    -                break;
    -            case 'tests':
    -                this.do_action({
    -                    name: _t("JS Tests"),
    -                    target: 'new',
    -                    type : 'ir.actions.act_url',
    -                    url: '/web/tests?mod=*'
    -                });
    -                break;
    -            case 'get_metadata':
    -                var ids = current_view.get_selected_ids();
    -                if (ids.length === 1) {
    -                    this.dataset.call('get_metadata', [ids]).done(function(result) {
    -                        var dialog = new instance.web.Dialog(this, {
    -                            title: _.str.sprintf(_t("Metadata (%s)"), self.dataset.model),
    -                            size: 'medium',
    -                        }, QWeb.render('ViewManagerDebugViewLog', {
    -                            perm : result[0],
    -                            format : instance.web.format_value
    -                        })).open();
    -                    });
    -                }
    -                break;
    -            case 'toggle_layout_outline':
    -                current_view.rendering_engine.toggle_layout_debugging();
    -                break;
    -            case 'set_defaults':
    -                current_view.open_defaults_dialog();
    -                break;
    -            case 'translate':
    -                this.do_action({
    -                    name: _t("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('fields_get', [false, {}]).done(function (fields) {
    -                    var $root = $('
    '); - _(fields).each(function (attributes, name) { - $root.append($('
    ').append($('

    ').text(name))); - var $attrs = $('
    ').appendTo($('
    ').appendTo($root)); - _(attributes).each(function (def, name) { - if (def instanceof Object) { - def = JSON.stringify(def); - } - $attrs - .append($('
    ').text(name)) - .append($('
    ').text(def)); - }); - }); - new instance.web.Dialog(self, { - title: _.str.sprintf(_t("Model %s fields"), - self.dataset.model), - }, $root).open(); - }); - break; - case 'edit_workflow': - return this.do_action({ - res_model : 'workflow', - domain : [['osv', '=', this.dataset.model]], - views: [[false, 'list'], [false, 'form'], [false, 'diagram']], - type : 'ir.actions.act_window', - view_type : 'list', - view_mode : 'list' - }); - case 'edit': - this.do_edit_resource($option.data('model'), $option.data('id'), { name : $option.text() }); - break; - case 'manage_filters': - this.do_action({ - res_model: 'ir.filters', - views: [[false, 'list'], [false, 'form']], - type: 'ir.actions.act_window', - context: { - search_default_my_filters: true, - search_default_model_id: this.dataset.model - } - }); - 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); - } - } - evt.currentTarget.selectedIndex = 0; - }, - do_edit_resource: function(model, id, action) { - action = _.extend({ - res_model : model, - res_id : id, - type : 'ir.actions.act_window', - view_type : 'form', - view_mode : 'form', - views : [[false, 'form']], - target : 'new', - flags : { - action_buttons : true, - form : { - resize_textareas : true - } - } - }, action || {}); - this.do_action(action); - }, - switch_mode: function (view_type, no_store, options) { - var self = this; - - return this.alive($.when(this._super.apply(this, arguments))).done(function () { - var controller = self.views[self.active_view].controller; - self.$el.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { - view: controller, - view_manager: self - })); - self.set_title(); - }); - }, - do_create_view: function(view_type) { - var self = this; - return this._super.apply(this, arguments).then(function() { - var view = self.views[view_type].controller; - view.set({ 'title': self.action.name }); - }); - }, - get_action_manager: function() { - var cur = this; - while ((cur = cur.getParent())) { - if (cur instanceof instance.web.ActionManager) { - return cur; - } - } - return undefined; - }, - set_title: function(title) { - this.$el.find('.oe_breadcrumb_title:first').html(this.get_action_manager().get_title()); - }, do_push_state: function(state) { - if (this.getParent() && this.getParent().do_push_state) { - state["view_type"] = this.active_view; - this.url_states[this.active_view] = state; - this.getParent().do_push_state(state); + if (this.action_manager) { + state.view_type = this.active_view.type; + this.action_manager.do_push_state(state); } - }, + }, do_load_state: function(state, warm) { var self = this, defs = []; - if (state.view_type && state.view_type !== this.active_view) { + if (state.view_type && state.view_type !== this.active_view.type) { defs.push( - this.views[this.active_view].deferred.then(function() { + this.active_view.created.then(function() { return self.switch_mode(state.view_type, true); }) ); } - $.when(this.views[this.active_view] ? this.views[this.active_view].deferred : $.when(), defs).done(function() { - self.views[self.active_view].controller.do_load_state(state, warm); + $.when(this.active_view ? this.active_view.created : null, defs).done(function() { + self.active_view.controller.do_load_state(state, warm); }); }, }); +instance.web.ViewManagerAction = instance.web.ViewManager.extend({ + template:"ViewManagerAction", + + init: function (parent, action) { + var flags = action.flags || {}; + if (!('auto_search' in flags)) { + flags.auto_search = action.auto_search !== false; + } + this.action = action; + this.action_manager = parent; + var dataset = new instance.web.DataSetSearch(this, action.res_model, action.context, action.domain); + this._super(parent, dataset, action.views, flags); + } +}); + instance.web.Sidebar = instance.web.Widget.extend({ init: function(parent) { var self = this; @@ -1331,7 +971,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ a.url = prefix + '&id=' + a.id + '&t=' + (new Date().getTime()); } }); - self.items['files'] = attachments; + self.items.files = attachments; self.redraw(); this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed); this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete); @@ -1519,9 +1159,7 @@ instance.web.View = instance.web.Widget.extend({ this.$el.hide(); }, is_active: function () { - var manager = this.getParent(); - return !manager || !manager.active_view - || manager.views[manager.active_view].controller === this; + return this.ViewManager.active_view.controller === this; }, /** * Wraps fn to only call it if the current view is the active one. If the * current view is not active, doesn't call fn. @@ -1544,7 +1182,8 @@ instance.web.View = instance.web.Widget.extend({ this.getParent().do_push_state(state); } }, - do_load_state: function(state, warm) { + do_load_state: function (state, warm) { + }, /** * Switches to a specific view type diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index f99a96b649b..656b1c9c080 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -476,57 +476,45 @@ - -
    - - - - - - - - - - - - - - -
    -

    - -

    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    - +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    + + + + +
    +
    +
    - - +
    +
    + +
    + +
    +
    - - - - -
    - -
    - - - -
    - -
    - - - - -
    -
    - - -
    -
    -
    From d8d6311b50d0d3446cab3f3386ad672ac486bab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 7 Oct 2014 16:27:43 +0200 Subject: [PATCH 05/29] [IMP] rewrite of search view/view manager Second part of a large refactoring. Lots of cleanup, simpler code organization, and it should be faster. --- addons/web/static/src/css/base.css | 554 ++++------- addons/web/static/src/css/base.sass | 499 ++++------ addons/web/static/src/js/search.js | 1404 +++++++++++---------------- addons/web/static/src/js/views.js | 346 +++++-- addons/web/static/src/xml/base.xml | 448 ++++----- 5 files changed, 1428 insertions(+), 1823 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 23f5782300c..61938b8fc59 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -752,59 +752,6 @@ filter: alpha(opacity=50); opacity: 0.5; } -.openerp .oe_sidebar { - white-space: nowrap; -} -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment { - height: 20px; - cursor: pointer; - padding-left: 6px; - margin-top: 6px; -} -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment span { - font-weight: bold; -} -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment .oe_hidden_input_file { - width: 200px; -} -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment:hover { - background-color: #efeff8; - background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); - background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} -.openerp .oe_sidebar .oe_dropdown_menu li .oe_sidebar_delete_item { - position: absolute; - top: 4px; - right: 4px; - display: none; - width: 12px; - height: 12px; - padding: 1px; - color: #8786b7; - line-height: 8px; - text-align: center; - font-weight: bold; - text-shadow: 0 1px 1px white; -} -.openerp .oe_sidebar .oe_dropdown_menu li .oe_sidebar_delete_item:hover { - text-decoration: none; - color: white; - background: #8786b7; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; -} -.openerp .oe_sidebar .oe_dropdown_menu li:hover .oe_sidebar_delete_item { - display: inline-block; -} .openerp .oe_loading { display: none; z-index: 1001; @@ -1044,6 +991,22 @@ .openerp .oe_application { width: 100%; height: 100%; + overflow: hidden; +} +.openerp .oe_application > div { + position: relative; + height: 100%; +} +.openerp .oe_application .oe-view-manager .oe-view-manager-debug { + margin-right: 5px; +} +.openerp .oe_application .oe-view-manager .oe-view-manager-content { + display: table-row; + height: 100%; +} +.openerp .oe_application .oe-view-manager-body { + position: relative; + height: 100%; } .openerp .oe_application .oe-view-manager-body a { color: #7C7BAD; @@ -1051,42 +1014,26 @@ .openerp .oe_application .oe-view-manager-body .oe-view-manager-view-kanban:not(:empty) { height: 100%; } -.openerp .oe_application > div { - position: relative; - height: 100%; -} -.openerp .oe_application > div > .oe_view_manager > .oe_view_manager_wrapper { - display: table-row; - height: 100%; -} -.openerp .oe_application > div > .oe_view_manager > .oe_view_manager_wrapper > div { - position: relative; - height: 100%; -} -.openerp .oe_application > div > .oe_view_manager > .oe_view_manager_wrapper > div > .oe_view_manager_body { +.openerp .oe_application .oe-view-manager-body > div { position: absolute; - position: static\9; top: 0; bottom: 0; left: 0; right: 0; overflow: auto; } -.openerp .oe_application .oe_breadcrumb_item:not(:last-child) { - max-width: 7em; - white-space: nowrap; - text-overflow: ellipsis; +.openerp .oe_application .oe_form_field_one2many .oe-view-manager-views { + position: initial; } -.openerp .oe_application .oe_breadcrumb_title > * { - display: inline-block; - overflow: hidden; - font-weight: bold; +.openerp .oe-view-manager { + display: table; + width: 100%; + height: 100%; } .openerp .oe-view-manager-header { - height: 90px; - overflow: visible; - border-bottom: 1px solid black; - display: none; + display: table-row; + background-color: #f0eeee; + border-bottom: 1px solid #afafb6; } .openerp .oe-view-manager-header .oe-right-toolbar { float: right; @@ -1095,26 +1042,34 @@ display: inline-block; } .openerp .oe-view-manager-header .row { - height: 45px; -} -.openerp .oe-view-manager-header .oe-view-title { - float: left; - font-size: 18px; - font-weight: bold; margin: 0; } -.openerp .oe-view-manager-header .oe-header-title { +.openerp .oe-view-manager-header .row:first-child { + padding-top: 3px; + padding-bottom: 3px; +} +.openerp .oe-view-manager-header .row:last-child { + padding-bottom: 10px; +} +.openerp .oe-view-manager-header .oe_tag { + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} +.openerp .oe-view-manager-header .oe-view-title { + font-size: 18px; padding-left: 0; + margin: 0; + background-color: #f0eeee; } .openerp .oe-view-manager-header .oe-view-manager-search-view { - float: right; padding-top: 5px; } .openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-kanban:before { content: ""; } .openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-list:before { - content: ""; + content: ""; } .openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-form:before { content: ""; @@ -1128,14 +1083,27 @@ .openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-gantt:before { content: ""; } +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-pivot:before { + content: ""; +} .openerp .oe-view-manager-header .oe-view-manager-buttons { - float: left; + display: inline-block; } .openerp .oe-view-manager-header .oe-view-manager-buttons > div { display: none; } -.openerp .oe-view-manager-header .oe_pager_value { +.openerp .oe-view-manager-header .oe-view-manager-sidebar { display: inline-block; + float: right; +} +.openerp .oe-view-manager-header .oe_form_buttons { + padding: 0; +} +.openerp .oe-view-manager-header .oe-pager-buttons { + min-height: 30px; +} +.openerp .oe_view_manager_inline > .oe-view-manager-header, .openerp .oe_view_manager_inlineview > .oe-view-manager-header { + display: none; } .openerp .oe_application > div > div > .oe-view-manager-body { position: absolute; @@ -1145,12 +1113,6 @@ right: 0; overflow: auto; } -.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_inlineview > .oe_view_manager_header { - display: none; -} .openerp .oe_popup_form { display: table; } @@ -1163,71 +1125,33 @@ .openerp .oe_popup_list_pager { float: right; } +.openerp .oe_popup_search { + width: 50%; + float: right; +} .openerp .oe_searchview { cursor: text; position: relative; - padding: 1px 0; - min-width: 400px; - width: 400px; - -moz-border-radius: 13px; - -webkit-border-radius: 13px; - border-radius: 13px; -} -.openerp .oe_searchview input, .openerp .oe_searchview textarea { - padding: 3px; - height: 14px; - font-size: 12px; - line-height: 18px; -} -.openerp .oe_searchview input:not([type]), .openerp .oe_searchview input[type="text"], .openerp .oe_searchview input[type="number"] { - width: 156px; - height: 22px; -} -.openerp .oe_searchview input[type="checkbox"] { - margin: 3px 3px 3px 4px; -} -.openerp .oe_searchview select { - margin: 2px 4px 2px 0; -} -.openerp .oe_searchview.oe_focused { - border-color: #a6a6fe; - -moz-box-shadow: 0 1px 2px #a6a6fe inset; - -webkit-box-shadow: 0 1px 2px #a6a6fe inset; - box-shadow: 0 1px 2px #a6a6fe inset; -} -.openerp .oe_searchview .oe_searchview_clear { - cursor: pointer; - position: absolute; - top: 3px; - right: 18px; - width: 15px; - height: 24px; + display: none; + height: auto; + padding-top: 1px; + padding-bottom: 1px; } .openerp .oe_searchview .oe_searchview_unfold_drawer { position: absolute; - top: 0; - right: 0; - color: #ccc; + top: 8px; + left: 5px; cursor: pointer; } -.openerp .oe_searchview .oe_searchview_unfold_drawer:hover { - color: #999; -} -.openerp .oe_searchview .oe_searchview_unfold_drawer:before { - position: absolute; - top: 7px; - right: 18px; - width: 0; -} .openerp .oe_searchview .oe_searchview_search { cursor: pointer; position: absolute; - left: 6px; + right: 6px; top: 8px; } .openerp .oe_searchview .oe_searchview_facets { min-height: 22px; - margin: 2px 35px 0 20px; + margin: 2px 20px 0 10px; } .openerp .oe_searchview .oe_searchview_facets * { vertical-align: top; @@ -1235,18 +1159,21 @@ font-size: 100%; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { - height: 20px; margin: 1px 0; font-size: 11px; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { outline: none; } +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .label { + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} .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; @@ -1257,9 +1184,6 @@ padding: 0; -webkit-font-smoothing: auto; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet span { - height: 18px; -} .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { border-color: #a6a6fe; -moz-box-shadow: 0 0 3px 1px #a6a6fe; @@ -1280,11 +1204,15 @@ font-size: 16px; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value { + border-left: 1px solid #afafb6; color: #4C4C4C; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:last-child { padding-right: 16px; } +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:first-child { + border-left: none; +} .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_remove { position: absolute; top: 3px; @@ -1310,8 +1238,8 @@ .openerp .oe_searchview .oe-autocomplete { display: none; position: absolute; - width: 300px; background-color: white; + width: 400px; border: 1px solid #afafb6; z-index: 666; margin-top: 2px; @@ -1319,9 +1247,6 @@ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; - -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); } .openerp .oe_searchview .oe-autocomplete ul { list-style-type: none; @@ -1353,216 +1278,133 @@ .openerp .oe_searchview .oe-autocomplete ul li.oe-separator:last-child { display: none; } -.openerp .oe_searchview_drawer_container { - overflow: auto; -} -.openerp .oe_searchview_drawer { - display: none; +.openerp .oe-search-options a { + padding-left: 25px; + padding-right: 25px; width: 100%; - cursor: default; - display: none; - overflow: hidden; - border-bottom: 1px solid #afafb6; - text-align: left; - padding: 8px 0; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } -.openerp .oe_searchview_drawer .badge { - font-size: 12px; - line-height: 12px; +.openerp .oe-search-options .oe-apply-filter { + margin-left: 25px; } -.openerp .oe_searchview_drawer > div:first-child { - border: none; - padding-left: 0; -} -.openerp .oe_searchview_drawer > div:first-child li:hover:not(.badge) { - background-color: #f0f0fa; -} -.openerp .oe_searchview_drawer .col-md-5 { - padding-left: 0; -} -.openerp .oe_searchview_drawer dl { - margin-bottom: 0; -} -.openerp .oe_searchview_drawer dt { - color: #7C7BAD; - font-size: 13px; - line-height: 24px; -} -.openerp .oe_searchview_drawer dd { - line-height: 24px; - font-size: 13px; - padding-top: 3px; -} -.openerp .oe_searchview_drawer h4, .openerp .oe_searchview_drawer h4 * { - margin: 0 0 0 2px; - padding-left: 20px; - cursor: pointer; - font-weight: normal; - display: inline-block; -} -.openerp .oe_searchview_drawer h4:hover, .openerp .oe_searchview_drawer h4 *:hover { - background-color: #f0f0fa; -} -.openerp .oe_searchview_drawer h4:before { - content: "▸ "; - color: #a3a3a3; -} -.openerp .oe_searchview_drawer button { - margin: 4px 0; -} -.openerp .oe_searchview_drawer .button { - border: none; - background: transparent; - padding: 0 2px; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; -} -.openerp .oe_searchview_drawer .oe_searchview_section ul { - margin: 0 8px; - padding: 0; - list-style: none; - display: inline; -} -.openerp .oe_searchview_drawer .oe_searchview_section li { - display: inline-block; - cursor: pointer; - position: relative; - margin-right: 8px; -} -.openerp .oe_searchview_drawer .oe_searchview_section li > span { - display: inline-block; - max-width: 250px; - text-overflow: ellipsis; - vertical-align: bottom; - overflow: hidden; -} -.openerp .oe_searchview_drawer form { - margin-left: 12px; -} -.openerp .oe_searchview_drawer form p { - margin: 4px 0; - line-height: 18px; -} -.openerp .oe_searchview_drawer form button { - margin: 0 0 8px -3px; -} -.openerp .oe_searchview_drawer .oe_searchview_savefilter form { +.openerp .oe-search-options .oe-add-filter-menu { display: none; } -.openerp .oe_searchview_drawer .oe_searchview_custom { - display: none; -} -.openerp .oe_searchview_drawer .oe_searchview_custom li { - cursor: pointer; - position: relative; - line-height: 14px; - margin-right: 0; -} -.openerp .oe_searchview_drawer .oe_searchview_custom li button { - position: absolute; - top: 0; - right: 5px; -} -.openerp .oe_searchview_drawer .oe_searchview_custom li a { +.openerp .oe-search-options .oe-add-condition { + width: auto; margin-left: 10px; - position: inherit; - visibility: hidden; - display: inline-block; + padding-left: 0; + padding-right: 10px; } -.openerp .oe_searchview_drawer .oe_searchview_custom li span:hover:not(.badge) { - background-color: #f0f0fa; -} -.openerp .oe_searchview_drawer .oe_searchview_custom li:hover a { - visibility: visible; -} -.openerp .oe_searchview_drawer .oe_searchview_custom label { - font-weight: normal; -} -.openerp .oe_searchview_drawer .oe_searchview_dashboard form { - display: none; - margin-top: 2px; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced { - overflow: auto; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced form { - display: none; - margin-top: 8px; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced button.oe_add_condition:before { - content: "Z"; - font-family: "entypoRegular" !important; - font-size: 24px; - font-weight: 300 !important; - margin-right: 4px; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced ul { - list-style: none; - padding: 0; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced li { - position: relative; - list-style: none; - margin: 0; - white-space: nowrap; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced li:first-child .searchview_extended_prop_or { - visibility: hidden; - margin-left: -14px; -} -.openerp .oe_searchview_drawer .oe_searchview_advanced .searchview_extended_prop_or { - opacity: 0.5; - margin-left: -14px; -} -.openerp .oe_searchview_drawer .oe_opened h4:before { - content: "▾ "; - position: relative; - top: -1px; -} -.openerp .oe_searchview_drawer .oe_opened form { +.openerp .oe-search-options .selected { display: block; } -.openerp .oe_searchview_drawer .oe_searchview_custom_delete, .openerp .oe_searchview_drawer .searchview_extended_delete_prop { - display: inline-block; - width: 12px; - height: 12px; - line-height: 12px; - padding: 1px; - color: #8786b7; - line-height: 8px; - text-align: center; +.openerp .oe-search-options .selected a { font-weight: bold; - text-shadow: 0 1px 1px white; } -.openerp .oe_searchview_drawer .oe_searchview_custom_delete:hover, .openerp .oe_searchview_drawer .searchview_extended_delete_prop:hover { - text-decoration: none; - color: white; - background: #8786b7; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; -} -.openerp .oe_searchview_drawer .oe_searchview_custom_delete { - display: none; +.openerp .oe-search-options .selected a:before { + font-family: FontAwesome; position: absolute; - bottom: 1px; - right: 4px; + left: 6px; + top: 3px; + content: ""; } -.openerp .oe_searchview_drawer .oe_searchview_custom_private:hover .oe_searchview_custom_delete, .openerp .oe_searchview_drawer .oe_searchview_custom_public:hover .oe_searchview_custom_delete { +.openerp .oe-search-options .oe-group-selector { + margin: 3px 25px; + min-width: calc(100% - 40px); + max-width: 250px; + width: auto; +} +.openerp .oe-search-options .divider { + display: none; +} +.openerp .oe-search-options .filters-menu .divider { + display: list-item; +} +.openerp .oe-search-options .closed-menu a:before { + font-family: FontAwesome; + position: absolute; + left: 12px; + top: 3px; + content: ""; +} +.openerp .oe-search-options .open-menu a:before { + font-family: FontAwesome; + position: absolute; + left: 9px; + top: 3px; + content: ""; +} +.openerp .oe-search-options .oe-select-group { + margin: 3px 25px; +} +.openerp .oe-search-options .oe-add-group { + display: none; +} +.openerp .oe-search-options .oe-save-name { + display: none; + margin: 3px 25px; +} +.openerp .oe-search-options .oe-save-name span { + white-space: nowrap; +} +.openerp .oe-search-options .oe-save-name span input { + margin-left: 0; +} +.openerp .oe-search-options .searchview_extended_prop_field { + display: inline; + width: calc(100% - 20px); +} +.openerp .oe-search-options .searchview_extended_prop_op { + margin: 3px 0; + width: calc(100% - 20px); +} +.openerp .oe-search-options .searchview_extended_delete_prop { + float: right; + display: inline; +} +.openerp .oe-search-options .searchview_extended_prop_value { + width: calc(100% - 20px); +} +.openerp .oe-search-options .searchview_extended_prop_value > select, .openerp .oe-search-options .searchview_extended_prop_value > input { + width: calc(100% - 20px); +} +.openerp .oe-search-options .searchview_extended_prop_value .oe_datepicker_main { + width: calc(100% - 20px); +} +.openerp .oe-search-options .searchview_extended_prop_value .oe_datepicker_master { + width: 100%; +} +.openerp .oe-search-options .searchview_extended_prop_value .fa-calendar { + margin-left: -21px; + cursor: pointer; +} +.openerp .oe-search-options span.fa-trash-o { + position: absolute; + top: 6px; + right: 10px; + cursor: pointer; +} +.openerp .oe-search-options .dropdown-menu > li { + position: relative; + white-space: nowrap; +} +.openerp .oe-search-options .dropdown-menu li.oe-filter-condition { + white-space: normal; + padding-left: 25px; + padding-right: 10px; + margin-bottom: 5px; +} +.openerp .oe-search-options a { display: inline-block; } -.openerp .oe_searchview_drawer .oe_searchview_custom_public:after { - content: ","; - font-family: "entypoRegular" !important; - font-size: 22px; - font-weight: 300 !important; - margin: 0 0 0 4px; - padding: 0; +.openerp input.oe-save-name { + width: auto; +} +.openerp .oe_pager_value { + display: inline-block; } .openerp .oe_view_nocontent { padding: 15px; @@ -1996,6 +1838,10 @@ .openerp .oe_form .oe_datepicker_root { display: inline-block; } +.openerp .oe_form .oe_datepicker_root .datepickerbutton { + margin-left: 5px; + cursor: pointer; +} .openerp .oe_form .oe_form_required input:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required select:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required textarea:not([disabled]):not([readonly]) { background-color: #D2D2FF !important; } @@ -2737,6 +2583,8 @@ } .openerp .oe_debug_view { float: left; + margin-top: 5px; + width: auto; } .openerp .oe_debug_view_log { font-size: 95%; @@ -2796,9 +2644,6 @@ font-size: 11px; background-color: #7c7bad; } -.openerp button, .openerp body { - line-height: normal; -} .openerp h1, .openerp h2 { font-weight: bold; } @@ -2836,9 +2681,6 @@ } @-moz-document url-prefix() { - .openerp .oe_searchview .oe_searchview_search { - top: -1px; - } .openerp .oe_form_field_many2one .oe_m2o_cm_button { line-height: 18px; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index e39f04f7559..845c00588b5 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -653,44 +653,6 @@ $sheet-padding: 16px border-right: 4px solid transparent border-top: 4px solid#404040 @include opacity(0.5) - .oe_sidebar - white-space: nowrap - .oe_dropdown_menu - .oe_sidebar_add_attachment - height: 20px - cursor: pointer - padding-left: 6px - margin-top: 6px - span - font-weight: bold - .oe_hidden_input_file - width: 200px - &:hover - @include vertical-gradient(#f0f0fa, #eeeef6) - @include box-shadow(none) - li - .oe_sidebar_delete_item - position: absolute - top: 4px - right: 4px - display: none - width: 12px - height: 12px - padding: 1px - color: #8786b7 - line-height: 8px - text-align: center - font-weight: bold - text-shadow: 0 1px 1px white - &:hover - text-decoration: none - color: white - background: #8786b7 - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) - @include radius(2px) - &:hover - .oe_sidebar_delete_item - display: inline-block // }}} // Loading {{{ .oe_loading @@ -899,67 +861,70 @@ $sheet-padding: 16px .oe_application width: 100% height: 100% + overflow: hidden + > div + position: relative + height: 100% + .oe-view-manager + .oe-view-manager-debug + margin-right: 5px + .oe-view-manager-content + display: table-row + height: 100% + .oe-view-manager-body + position: relative + height: 100% a color: $link-color .oe-view-manager-view-kanban:not(:empty) height: 100% - > div - position: relative - height: 100% - > .oe_view_manager - > .oe_view_manager_wrapper - display: table-row - height: 100% - > div - position: relative - height: 100% - > .oe_view_manager_body - position: absolute - position: static\9 - top: 0 - bottom: 0 - left: 0 - right: 0 - overflow: auto + > div + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + overflow: auto + .oe_form_field_one2many + .oe-view-manager-views + position: initial - - .oe_breadcrumb_item:not(:last-child) - max-width: 7em - white-space: nowrap - text-overflow: ellipsis - .oe_breadcrumb_title > * - display: inline-block - overflow: hidden - font-weight: bold // }}} // ViewManager common {{{ + .oe-view-manager + display: table + width: 100% + height: 100% .oe-view-manager-header - height: 90px - overflow: visible - border-bottom: 1px solid black - display: none + display: table-row + background-color: rgb(240, 238, 238) + border-bottom: 1px solid #afafb6 .oe-right-toolbar float: right > div display: inline-block .row - height: 45px - .oe-view-title - float: left - font-size: 18px - font-weight: bold margin: 0 - .oe-header-title + .row:first-child + padding-top: 3px + padding-bottom: 3px + .row:last-child + padding-bottom: 10px + .oe_tag + @include radius(0px) + .oe-view-title + font-size: 18px padding-left: 0 + margin: 0 + background-color: rgb(240, 238, 238) .oe-view-manager-search-view - float: right padding-top: 5px .oe-view-manager-switch .oe-vm-switch-kanban:before content: "\f009" .oe-vm-switch-list:before - content: "\f0c9" + content: "" .oe-vm-switch-form:before content: "\f044" .oe-vm-switch-graph:before @@ -968,12 +933,22 @@ $sheet-padding: 16px content: "\f073" .oe-vm-switch-gantt:before content: "\f0ae" + .oe-vm-switch-pivot:before + content: "\f0ce" .oe-view-manager-buttons - float: left + display: inline-block > div display: none - .oe_pager_value - display: inline-block + .oe-view-manager-sidebar + display: inline-block + float: right + .oe_form_buttons + padding: 0 + .oe-pager-buttons + min-height: 30px + .oe_view_manager_inline, .oe_view_manager_inlineview + > .oe-view-manager-header + display: none .oe_application > div > div @@ -985,26 +960,6 @@ $sheet-padding: 16px right: 0 overflow: auto // }}} - // ViewManager application {{{ - // .oe_view_manager_current - // height: 100% - // > .oe_view_manager_header - // border-bottom: 1px solid #cacaca - // @include vertical-gradient(#fcfcfc, #dedede) - // @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) - // .oe_header_row - // td - // padding: 8px - // .oe_header_row:first-child - // td - // padding-top: 8px - // }}} - // ViewManager inline {{{ - .oe_view_manager_inline, .oe_view_manager_inlineview - height: 100% - > .oe_view_manager_header - display: none - // }}} // FormPopup {{{ .oe_popup_form display: table @@ -1016,77 +971,48 @@ $sheet-padding: 16px // End of customize .oe_popup_list_pager float: right + .oe_popup_search + width: 50% + float: right // }}} // SearchView {{{ .oe_searchview cursor: text position: relative - padding: 1px 0 - min-width: 400px - width: 400px - @include radius(13px) - input, textarea - padding: 3px - height: 14px - font-size: 12px - line-height: 18px - //Customize searchview input, select - input:not([type]), input[type="text"], input[type="number"] - width: 156px - height: 22px - input[type="checkbox"] - margin: 3px 3px 3px 4px - select - margin: 2px 4px 2px 0 - //End of customize - &.oe_focused - border-color: $tag-border-selected - @include box-shadow(0 1px 2px $tag-border-selected inset) - .oe_searchview_clear - cursor: pointer - position: absolute - top: 3px - right: 18px - width: 15px - height: 24px + display: none + height: auto + padding-top: 1px + padding-bottom: 1px .oe_searchview_unfold_drawer position: absolute - top: 0 - right: 0 - color: #ccc + top: 8px + left: 5px cursor: pointer - &:hover - color: #999 - &:before - position: absolute - top: 7px - right: 18px - width: 0 .oe_searchview_search cursor: pointer position: absolute - left: 6px + right: 6px top: 8px .oe_searchview_facets min-height: 22px - margin: 2px 35px 0 20px + margin: 2px 20px 0 10px * vertical-align: top display: inline-block font-size: 100% .oe_searchview_facet - height: 20px margin: 1px 0 font-size: 11px &:focus outline: none + .label + @include radius(0px) .oe_searchview_input padding: 0 0 0 6px font-size: 12px height: 16px - margin-top: 3px &:focus outline: none .oe_searchview_facet @@ -1095,8 +1021,6 @@ $sheet-padding: 16px padding: 0 -webkit-font-smoothing: auto // spacing for opera, FF - span - height: 18px &:focus border-color: $tag-border-selected @include box-shadow(0 0 3px 1px $tag-border-selected) @@ -1110,9 +1034,12 @@ $sheet-padding: 16px .oe_facet_category.oe_i font-size: 16px .oe_facet_value + border-left: 1px solid #afafb6 color: #4C4C4C &:last-child padding-right: 16px + &:first-child + border-left: none .oe_facet_remove position: absolute top: 3px @@ -1135,14 +1062,13 @@ $sheet-padding: 16px .oe-autocomplete display: none position: absolute - width: 300px background-color: white + width: 400px border: 1px solid #afafb6 z-index: 666 margin-top: 2px cursor: default @include radius(3px) - @include box-shadow(0 1px 4px rgba(0, 0, 0, 0.3)) ul list-style-type: none padding-left: 0 @@ -1166,180 +1092,112 @@ $sheet-padding: 16px li.oe-separator:last-child display: none - - .oe_searchview_drawer_container - overflow: auto - .oe_searchview_drawer - display: none - width: 100% - cursor: default - display: none - overflow: hidden - border-bottom: 1px solid $tag-border - text-align: left - padding: 8px 0 - .badge - font-size: 12px - line-height: 12px - > div:first-child - border: none - padding-left: 0 - li:hover:not(.badge) - background-color: $hover-background - .col-md-5 - padding-left: 0 - dl - margin-bottom: 0 - dt - color: $section-title-color - font-size: 13px - line-height: 24px - dd - line-height: 24px - font-size: 13px - padding-top: 3px - h4, h4 * - margin: 0 0 0 2px - padding-left: 20px - cursor: pointer - font-weight: normal - display: inline-block - &:hover - background-color: $hover-background - h4:before - content: "▸ " - color: #a3a3a3 - button - margin: 4px 0 - .button - border: none - background: transparent - padding: 0 2px - @include box-shadow(none) - @include radius(0) - .oe_searchview_section - ul - margin: 0 8px - padding: 0 - list-style: none - display: inline - li - display: inline-block - cursor: pointer - position: relative - margin-right: 8px - > span - display: inline-block - max-width: 250px - text-overflow: ellipsis - vertical-align: bottom - overflow: hidden - form - margin-left: 12px - p - margin: 4px 0 - line-height: 18px - button - margin: 0 0 8px -3px // Managed margin-left according bootstrap3 - .oe_searchview_savefilter - form - display: none - .oe_searchview_custom + .oe-search-options + a + padding-left: 25px + padding-right: 25px + width: 100% + -webkit-user-select: none + -moz-user-select: none + user-select: none + .oe-apply-filter + margin-left: 25px + .oe-add-filter-menu display: none - li - cursor: pointer - position: relative - line-height: 14px - margin-right: 0 - button - position: absolute - top: 0 - right: 5px - a - margin-left: 10px - position: inherit - visibility: hidden - display: inline-block - span:hover:not(.badge) - background-color: $hover-background - li:hover a - visibility: visible - //Customize for searchview label - label - font-weight: normal - //End of Customize - .oe_searchview_dashboard - form - display: none - margin-top: 2px + .oe-add-condition + width: auto + margin-left: 10px + padding-left: 0 + padding-right: 10px + .selected + display: block + a + font-weight: bold + a:before + font-family: FontAwesome + position: absolute + left: 6px + top: 3px + content: "\f00c" + .oe-group-selector + margin: 3px 25px + min-width: calc(100% - 40px) + max-width: 250px + width: auto + .divider + display: none + .filters-menu + .divider + display: list-item + .closed-menu + a:before + font-family: FontAwesome + position: absolute + left: 12px + top: 3px + content: "\f0da" + .open-menu + a:before + font-family: FontAwesome + position: absolute + left: 9px + top: 3px + content: "\f0d7" - .oe_searchview_advanced - overflow: auto - form - display: none - margin-top: 8px - button.oe_add_condition:before - content: "Z" - font-family: "entypoRegular" !important - font-size: 24px - font-weight: 300 !important - margin-right: 4px - ul - list-style: none - padding: 0 - li - position: relative - list-style: none - margin: 0 + .oe-select-group + margin: 3px 25px + .oe-add-group + display: none + .oe-save-name + display: none + margin: 3px 25px + span white-space: nowrap - &:first-child .searchview_extended_prop_or - visibility: hidden - margin-left: -14px - .searchview_extended_prop_or - opacity: 0.5 - margin-left: -14px //Customize 'or' in searchview - .oe_opened - h4:before - content: "▾ " - position: relative - top: -1px - form - display: block - - // delete buttons - .oe_searchview_custom_delete, .searchview_extended_delete_prop - display: inline-block - width: 12px - height: 12px - line-height: 12px - padding: 1px - color: #8786b7 - line-height: 8px - text-align: center - font-weight: bold - text-shadow: 0 1px 1px white - &:hover - text-decoration: none - color: white - background: #8786b7 - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) - @include radius(2px) - .oe_searchview_custom_delete - display: none + input + margin-left: 0 + .searchview_extended_prop_field + display: inline + width: calc(100% - 20px) + + .searchview_extended_prop_op + margin: 3px 0 + width: calc(100% - 20px) + .searchview_extended_delete_prop + float: right + display: inline + .searchview_extended_prop_value + width: calc(100% - 20px) + > select, > input + width: calc(100% - 20px) + .oe_datepicker_main + width: calc(100% - 20px) + .oe_datepicker_master + width: 100% + .fa-calendar + margin-left: -21px + cursor: pointer + span.fa-trash-o position: absolute - bottom: 1px - right: 4px - .oe_searchview_custom_private, .oe_searchview_custom_public - &:hover - .oe_searchview_custom_delete - display: inline-block - .oe_searchview_custom_public:after - content: "," - font-family: "entypoRegular" !important - font-size: 22px - font-weight: 300 !important - margin: 0 0 0 4px - padding: 0 + top: 6px + right: 10px + cursor: pointer + .dropdown-menu + > li + position: relative + white-space: nowrap + li.oe-filter-condition + white-space: normal + padding-left: 25px + padding-right: 10px + margin-bottom: 5px + a + display: inline-block + + input.oe-save-name + width: auto + .oe_pager_value + display: inline-block + // }}} // Views Common {{{ .oe_view_nocontent @@ -1674,6 +1532,9 @@ $sheet-padding: 16px width: auto .oe_datepicker_root display: inline-block + .datepickerbutton + margin-left: 5px + cursor: pointer .oe_form_required input:not([disabled]):not([readonly]), select:not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly]) background-color: #D2D2FF !important @@ -2250,6 +2111,8 @@ $sheet-padding: 16px content: "[newline]" .oe_debug_view float: left + margin-top: 5px + width: auto .oe_debug_view_log font-size: 95% line-height: 1.2em @@ -2300,8 +2163,6 @@ $sheet-padding: 16px background-color: #7c7bad // Customize for global tags - button, body - line-height: normal h1,h2 font-weight: bold h3 @@ -2337,8 +2198,6 @@ $sheet-padding: 16px @-moz-document url-prefix() .openerp - .oe_searchview .oe_searchview_search - top: -1px .oe_form_field_many2one .oe_m2o_cm_button line-height: 18px .oe_webclient diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index c07a82620a9..29d345b70f2 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -330,18 +330,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } }, // search button - 'click i.oe_searchview_search': function (e) { + 'click div.oe_searchview_search': function (e) { e.stopImmediatePropagation(); this.do_search(); }, - 'click .oe_searchview_clear': function (e) { - e.stopImmediatePropagation(); - this.query.reset(); - }, 'click .oe_searchview_unfold_drawer': function (e) { e.stopImmediatePropagation(); - if (this.drawer) - this.drawer.toggle(); + $(e.target).toggleClass('fa-chevron-right') + .toggleClass('fa-chevron-down'); + localStorage.visible_search_menu = !(localStorage.visible_search_menu === 'true'); + this.toggle_buttons(); }, 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { switch(e.which) { @@ -379,88 +377,195 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea disable_custom_filters: false, }); this._super(parent); + this.query = undefined; this.dataset = dataset; - this.model = dataset.model; this.view_id = view_id; - + this.search_fields = []; + this.filters = []; + this.groupbys = []; + this.visible_filters = !!(localStorage.visible_search_menu === 'true'); + this.input_subviews = []; // for user input in searchbar this.defaults = defaults || {}; - this.has_defaults = !_.isEmpty(this.defaults); + this.headless = this.options.hidden && _.isEmpty(this.defaults); + this.$buttons = this.options.$buttons; - this.headless = this.options.hidden && !this.has_defaults; - - this.input_subviews = []; - this.view_manager = null; - this.$view_manager_header = null; - - this.ready = $.Deferred(); - this.drawer_ready = $.Deferred(); - this.fields_view_get = $.Deferred(); - this.drawer = new instance.web.SearchViewDrawer(parent, this); - - }, + this.filter_menu = undefined; + this.groupby_menu = undefined; + this.favorite_menu = undefined + }, start: function() { - var self = this; - var p = this._super(); - - this.$view_manager_header = this.$el.parents(".oe_view_manager_header").first(); - + if (this.headless) { + this.$el.hide(); + } + this.toggle_visibility(false); + this.$facets_container = this.$('div.oe_searchview_facets'); this.setup_global_completion(); this.query = new my.SearchQuery() .on('add change reset remove', this.proxy('do_search')) .on('change', this.proxy('renderChangedFacets')) .on('add reset remove', this.proxy('renderFacets')); - - if (this.options.hidden) { - this.$el.hide(); - } - if (this.headless) { - this.ready.resolve(); - } else { - var load_view = instance.web.fields_view_get({ - model: this.dataset._model, - view_id: this.view_id, - view_type: 'search', - context: this.dataset.get_context(), - }); - - this.alive($.when(load_view)).then(function (r) { - self.fields_view_get.resolve(r); - return self.search_view_loaded(r); - }).fail(function () { - self.ready.reject.apply(null, arguments); - }); - } - - var view_manager = this.getParent(); - while (!(view_manager instanceof instance.web.ViewManager) && - view_manager && view_manager.getParent) { - view_manager = view_manager.getParent(); - } - - if (view_manager) { - this.view_manager = view_manager; - view_manager.on('switch_mode', this, function (e) { - self.drawer.toggle(false); - }); - } - return $.when(p, this.ready); + var load_view = instance.web.fields_view_get({ + model: this.dataset._model, + view_id: this.view_id, + view_type: 'search', + context: this.dataset.get_context(), + }); + this.$('.oe_searchview_unfold_drawer') + .toggleClass('fa-chevron-right', !this.visible_filters) + .toggleClass('fa-chevron-down', this.visible_filters); + return $.when(this._super(), this.alive($.when(load_view)) + .then(this.view_loaded.bind(this))); }, + view_loaded: function (r) { + var self = this; + this.fields_view_get = r; + this.prepare_search_inputs(); + if (this.$buttons) { - set_drawer: function (drawer) { - this.drawer = drawer; + var fields_def = new instance.web.Model(this.dataset.model).call('fields_get', { + context: this.dataset.context + }); + + this.groupby_menu = new my.GroupByMenu(this, this.groupbys, fields_def); + this.filter_menu = new my.FilterMenu(this, this.filters, fields_def); + this.favorite_menu = new my.FavoriteMenu(this, this.query, this.dataset.model); + + this.filter_menu.appendTo(this.$buttons); + this.groupby_menu.appendTo(this.$buttons); + var custom_filters_ready = this.favorite_menu.appendTo(this.$buttons); + } + return $.when(custom_filters_ready).then(this.proxy('set_default_filters')); }, + set_default_filters: function (a, b) { + var self = this, + default_custom_filter = this.$buttons && this.favorite_menu.get_default_filter(); + if (default_custom_filter) { + return this.favorite_menu.toggle_filter(default_custom_filter, true); + } + if (!_.isEmpty(this.defaults)) { + var inputs = this.search_fields.concat(this.filters, this.groupbys), + defaults = _.invoke(inputs, 'facet_for_defaults', this.defaults); + return $.when.apply(null, defaults).then(function () { + self.query.reset(_(arguments).compact(), {preventSearch: true}); + }); + } + this.query.reset([], {preventSearch: true}); + return $.when(); + }, + /** + * Performs the search view collection of widget data. + * + * If the collection went well (all fields are valid), then triggers + * :js:func:`instance.web.SearchView.on_search`. + * + * If at least one field failed its validation, triggers + * :js:func:`instance.web.SearchView.on_invalid` instead. + * + * @param [_query] + * @param {Object} [options] + */ + do_search: function (_query, options) { + if (options && options.preventSearch) { + return; + } + var search = this.build_search_data(); + this.trigger('search_data', search.domains, search.contexts, search.groupbys); + }, + /** + * Extract search data from the view's facets. + * + * Result is an object with 3 (own) properties: + * + * domains + * Array of domains + * contexts + * Array of contexts + * groupbys + * Array of domains, in groupby order rather than view order + * + * @return {Object} + */ + build_search_data: function () { + var domains = [], contexts = [], groupbys = []; + this.query.each(function (facet) { + var field = facet.get('field'); + var domain = field.get_domain(facet); + if (domain) { + domains.push(domain); + } + var context = field.get_context(facet); + if (context) { + contexts.push(context); + } + var group_by = field.get_groupby(facet); + if (group_by) { + groupbys.push.apply(groupbys, group_by); + } + }); + return { + domains: domains, + contexts: contexts, + groupbys: groupbys, + }; + }, toggle_visibility: function (is_visible) { - this.$el.toggle(is_visible); + this.$el.toggle(!this.headless && is_visible); + this.$buttons && this.$buttons.toggle(!this.headless && is_visible && this.visible_filters); }, - - show: function () { - this.$el.show(); + toggle_buttons: function (is_visible) { + this.visible_filters = is_visible || !this.visible_filters; + this.$buttons && this.$buttons.toggle(this.visible_filters); }, - hide: function () { - this.$el.hide(); + /** + * Sets up search view's view-wide auto-completion widget + */ + setup_global_completion: function () { + var self = this; + this.autocomplete = new my.AutoComplete(this, { + source: this.proxy('complete_global_search'), + select: this.proxy('select_completion'), + delay: 0, + get_search_string: function () { + return self.$('div.oe_searchview_input').text(); + }, + }); + this.autocomplete.appendTo(this.$el); + }, + /** + * Provide auto-completion result for req.term (an array to `resp`) + * + * @param {Object} req request to complete + * @param {String} req.term searched term to complete + * @param {Function} resp response callback + */ + complete_global_search: function (req, resp) { + var inputs = this.search_fields.concat(this.filters, this.groupbys); + $.when.apply(null, _(inputs).chain() + .filter(function (input) { return input.visible(); }) + .invoke('complete', req.term) + .value()).then(function () { + resp(_(arguments).chain() + .compact() + .flatten(true) + .value()); + }); + }, + /** + * Action to perform in case of selection: create a facet (model) + * and add it to the search collection + * + * @param {Object} e selection event, preventDefault to avoid setting value on object + * @param {Object} ui selection information + * @param {Object} ui.item selected completion item + */ + select_completion: function (e, ui) { + e.preventDefault(); + var input_index = _(this.input_subviews).indexOf( + this.subviewForRoot( + this.$('div.oe_searchview_input:focus')[0])); + this.query.add(ui.item.facet, {at: input_index / 2}); }, - subviewForRoot: function (subview_root) { return _(this.input_subviews).detect(function (subview) { return subview.$el[0] === subview_root; @@ -485,77 +590,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea this.subviewForRoot(subview_root), +1, true) .$el.focus(); }, - - /** - * Sets up search view's view-wide auto-completion widget - */ - setup_global_completion: function () { - var self = this; - this.autocomplete = new instance.web.search.AutoComplete(this, { - source: this.proxy('complete_global_search'), - select: this.proxy('select_completion'), - delay: 0, - get_search_string: function () { - return self.$('div.oe_searchview_input').text(); - }, - width: this.$el.width(), - }); - this.autocomplete.appendTo(this.$el); - }, - /** - * Provide auto-completion result for req.term (an array to `resp`) - * - * @param {Object} req request to complete - * @param {String} req.term searched term to complete - * @param {Function} resp response callback - */ - complete_global_search: function (req, resp) { - $.when.apply(null, _(this.drawer.inputs).chain() - .filter(function (input) { return input.visible(); }) - .invoke('complete', req.term) - .value()).then(function () { - resp(_(arguments).chain() - .compact() - .flatten(true) - .value()); - }); - }, - - /** - * Action to perform in case of selection: create a facet (model) - * and add it to the search collection - * - * @param {Object} e selection event, preventDefault to avoid setting value on object - * @param {Object} ui selection information - * @param {Object} ui.item selected completion item - */ - select_completion: function (e, ui) { - e.preventDefault(); - - var input_index = _(this.input_subviews).indexOf( - this.subviewForRoot( - this.$('div.oe_searchview_input:focus')[0])); - this.query.add(ui.item.facet, {at: input_index / 2}); - }, - childFocused: function () { - this.$el.addClass('oe_focused'); - }, - childBlurred: function () { - var val = this.$el.val(); - this.$el.val(''); - this.$el.removeClass('oe_focused') - .trigger('blur'); - this.autocomplete.close(); - }, - /** - * Call the renderFacets method with the correct arguments. - * This is due to the fact that change events are called with two arguments - * (model, options) while add, reset and remove events are called with - * (collection, model, options) as arguments - */ - renderChangedFacets: function (model, options) { - this.renderFacets(undefined, model, options); - }, /** * @param {openerp.web.search.SearchQuery | undefined} Undefined if event is change * @param {openerp.web.search.Facet} @@ -564,20 +598,19 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea renderFacets: function (collection, model, options) { var self = this; var started = []; - var $e = this.$('div.oe_searchview_facets'); _.invoke(this.input_subviews, 'destroy'); this.input_subviews = []; var i = new my.InputView(this); - started.push(i.appendTo($e)); + started.push(i.appendTo(this.$facets_container)); this.input_subviews.push(i); this.query.each(function (facet) { var f = new my.FacetView(this, facet); - started.push(f.appendTo($e)); + started.push(f.appendTo(self.$facets_container)); self.input_subviews.push(f); var i = new my.InputView(this); - started.push(i.appendTo($e)); + started.push(i.appendTo(self.$facets_container)); self.input_subviews.push(i); }, this); _.each(this.input_subviews, function (childView) { @@ -598,320 +631,77 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea input_to_focus.$el.focus(); }); }, - - search_view_loaded: function(data) { - var self = this; - this.fields_view = data; - if (data.type !== 'search' || - data.arch.tag !== 'search') { - throw new Error(_.str.sprintf( - "Got non-search view after asking for a search view: type %s, arch root %s", - data.type, data.arch.tag)); - } - - return this.drawer_ready - .then(this.proxy('setup_default_query')) - .then(function () { - self.trigger("search_view_loaded", data); - self.ready.resolve(); - }); + childFocused: function () { + this.$el.addClass('active'); }, - setup_default_query: function () { - // Hacky implementation of CustomFilters#facet_for_defaults ensure - // CustomFilters will be ready (and CustomFilters#filters will be - // correctly filled) by the time this method executes. - var custom_filters = this.drawer.custom_filters.filters; - if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) { - // Check for any is_default custom filter - var personal_filter = _(custom_filters).find(function (filter) { - return filter.user_id && filter.is_default; - }); - if (personal_filter) { - this.drawer.custom_filters.toggle_filter(personal_filter, true); - return; - } - - var global_filter = _(custom_filters).find(function (filter) { - return !filter.user_id && filter.is_default; - }); - if (global_filter) { - this.drawer.custom_filters.toggle_filter(global_filter, true); - return; - } - } - // No custom filter, or no is_default custom filter, apply view defaults - this.query.reset(_(arguments).compact(), {preventSearch: true}); + childBlurred: function () { + var val = this.$el.val(); + this.$el.val(''); + this.$el.removeClass('active') + .trigger('blur'); + this.autocomplete.close(); }, /** - * Extract search data from the view's facets. - * - * Result is an object with 4 (own) properties: - * - * errors - * An array of any error generated during data validation and - * extraction, contains the validation error objects - * domains - * Array of domains - * contexts - * Array of contexts - * groupbys - * Array of domains, in groupby order rather than view order - * - * @return {Object} + * Call the renderFacets method with the correct arguments. + * This is due to the fact that change events are called with two arguments + * (model, options) while add, reset and remove events are called with + * (collection, model, options) as arguments */ - build_search_data: function () { - var domains = [], contexts = [], groupbys = [], errors = []; + renderChangedFacets: function (model, options) { + this.renderFacets(undefined, model, options); + }, + // it should parse the arch field of the view, instantiate the corresponding + // filters/fields, and put them in the correct variables: + // * this.search_fields is a list of all the fields, + // * this.filters: groups of filters + // * this.group_by: group_bys + prepare_search_inputs: function () { + var self = this, + arch = this.fields_view_get.arch; - this.query.each(function (facet) { - var field = facet.get('field'); - try { - var domain = field.get_domain(facet); - if (domain) { - domains.push(domain); - } - var context = field.get_context(facet); - if (context) { - contexts.push(context); - } - var group_by = field.get_groupby(facet); - if (group_by) { - groupbys.push.apply(groupbys, group_by); - } - } catch (e) { - if (e instanceof instance.web.search.Invalid) { - errors.push(e); - } else { - throw e; + var filters = [].concat.apply([], _.map(arch.children, function (item) { + return item.tag !== 'group' ? eval_item(item) : item.children.map(eval_item); + })); + function eval_item (item) { + var category = 'filters'; + if (item.attrs.context) { + var context = instance.web.pyeval.eval('context', item.attrs.context); + if (context.group_by) { + category = 'group_by'; } } - }); - return { - domains: domains, - contexts: contexts, - groupbys: groupbys, - errors: errors - }; - }, - /** - * Performs the search view collection of widget data. - * - * If the collection went well (all fields are valid), then triggers - * :js:func:`instance.web.SearchView.on_search`. - * - * If at least one field failed its validation, triggers - * :js:func:`instance.web.SearchView.on_invalid` instead. - * - * @param [_query] - * @param {Object} [options] - */ - do_search: function (_query, options) { - if (options && options.preventSearch) { - return; + return { + item: item, + category: category, + } } - var search = this.build_search_data(); - if (!_.isEmpty(search.errors)) { - this.on_invalid(search.errors); - return; - } - this.trigger('search_data', search.domains, search.contexts, search.groupbys); - }, - /** - * Triggered after the SearchView has collected all relevant domains and - * contexts. - * - * It is provided with an Array of domains and an Array of contexts, which - * may or may not be evaluated (each item can be either a valid domain or - * context, or a string to evaluate in order in the sequence) - * - * It is also passed an array of contexts used for group_by (they are in - * the correct order for group_by evaluation, which contexts may not be) - * - * @event - * @param {Array} domains an array of literal domains or domain references - * @param {Array} contexts an array of literal contexts or context refs - * @param {Array} groupbys ordered contexts which may or may not have group_by keys - */ - /** - * Triggered after a validation error in the SearchView fields. - * - * Error objects have three keys: - * * ``field`` is the name of the invalid field - * * ``value`` is the invalid value - * * ``message`` is the (in)validation message provided by the field - * - * @event - * @param {Array} errors a never-empty array of error objects - */ - on_invalid: function (errors) { - this.do_notify(_t("Invalid Search"), _t("triggered from search view")); - this.trigger('invalid_search', errors); - }, + var current_group = [], + current_category = 'filters', + categories = {filters: this.filters, group_by: this.groupbys}; - // The method appendTo is overwrited to be able to insert the drawer anywhere - appendTo: function ($searchview_parent, $searchview_drawer_node) { - var $searchview_drawer_node = $searchview_drawer_node || $searchview_parent; - - return $.when( - this._super($searchview_parent), - this.drawer.appendTo($searchview_drawer_node) - ); - }, - - destroy: function () { - this.drawer.destroy(); - this.getParent().destroy.call(this); - } -}); - -instance.web.SearchViewDrawer = instance.web.Widget.extend({ - template: "SearchViewDrawer", - - init: function(parent, searchview) { - this._super(parent); - this.searchview = searchview; - this.searchview.set_drawer(this); - this.ready = searchview.drawer_ready; - this.controls = []; - this.inputs = []; - }, - - toggle: function (visibility) { - this.$el.toggle(visibility); - var $view_manager_body = this.$el.closest('.oe_view_manager_body'); - if ($view_manager_body.length) { - $view_manager_body.scrollTop(0); - } - }, - - start: function() { - var self = this; - if (this.searchview.headless) return $.when(this._super(), this.searchview.ready); - var filters_ready = this.searchview.fields_view_get - .then(this.proxy('prepare_filters')); - return $.when(this._super(), filters_ready).then(function () { - var defaults = arguments[1][0]; - self.ready.resolve.apply(null, defaults); + _.each(filters.concat({category:'filters', item: 'separator'}), function (filter) { + if (filter.item.tag === 'filter' && filter.category === current_category) { + return current_group.push(new my.Filter(filter.item, self)); + } + if (current_group.length) { + var group = new my.FilterGroup(current_group, self); + categories[current_category].push(group); + current_group = []; + } + if (filter.item.tag === 'field') { + var attrs = filter.item.attrs, + field = self.fields_view_get.fields[attrs.name], + Obj = my.fields.get_any([attrs.widget, field.type]); + if (Obj) { + self.search_fields.push(new (Obj) (filter.item, field, self)); + } + } + if (filter.item.tag === 'filter') { + current_group.push(new my.Filter(filter.item, self)); + } + current_category = filter.category; }); }, - prepare_filters: function (data) { - this.make_widgets( - data['arch'].children, - data.fields); - - this.add_common_inputs(); - - // build drawer - var in_drawer = this.select_for_drawer(); - - var $first_col = this.$(".col-md-7"), - $snd_col = this.$(".col-md-5"); - - var add_custom_filters = in_drawer[0].appendTo($first_col), - add_filters = in_drawer[1].appendTo($first_col), - add_rest = $.when.apply(null, _(in_drawer.slice(2)).invoke('appendTo', $snd_col)), - defaults_fetched = $.when.apply(null, _(this.inputs).invoke( - 'facet_for_defaults', this.searchview.defaults)); - - return $.when(defaults_fetched, add_custom_filters, add_filters, add_rest); - }, - /** - * Sets up thingie where all the mess is put? - */ - select_for_drawer: function () { - return _(this.inputs).filter(function (input) { - return input.in_drawer(); - }); - }, - - /** - * Builds a list of widget rows (each row is an array of widgets) - * - * @param {Array} items a list of nodes to convert to widgets - * @param {Object} fields a mapping of field names to (ORM) field attributes - * @param {Object} [group] group to put the new controls in - */ - make_widgets: function (items, fields, group) { - if (!group) { - group = new instance.web.search.Group( - this, 'q', {attrs: {string: _t("Filters")}}); - } - var self = this; - var filters = []; - _.each(items, function (item) { - if (filters.length && item.tag !== 'filter') { - group.push(new instance.web.search.FilterGroup(filters, group)); - filters = []; - } - - switch (item.tag) { - case 'separator': case 'newline': - break; - case 'filter': - filters.push(new instance.web.search.Filter(item, group)); - break; - case 'group': - self.add_separator(); - self.make_widgets(item.children, fields, - new instance.web.search.Group(group, 'w', item)); - self.add_separator(); - break; - case 'field': - var field = this.make_field( - item, fields[item['attrs'].name], group); - group.push(field); - // filters - self.make_widgets(item.children, fields, group); - break; - } - }, this); - - if (filters.length) { - group.push(new instance.web.search.FilterGroup(filters, this)); - } - }, - - add_separator: function () { - if (!(_.last(this.inputs) instanceof instance.web.search.Separator)) - new instance.web.search.Separator(this); - }, - /** - * Creates a field for the provided field descriptor item (which comes - * from fields_view_get) - * - * @param {Object} item fields_view_get node for the field - * @param {Object} field fields_get result for the field - * @param {Object} [parent] - * @returns instance.web.search.Field - */ - make_field: function (item, field, parent) { - // M2O combined with selection widget is pointless and broken in search views, - // but has been used in the past for unsupported hacks -> ignore it - if (field.type === "many2one" && item.attrs.widget === "selection"){ - item.attrs.widget = undefined; - } - var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); - if(obj) { - return new (obj) (item, field, parent || this); - } else { - console.group('Unknown field type ' + field.type); - console.error('View node', item); - console.info('View field', field); - console.info('In view', this); - console.groupEnd(); - return null; - } - }, - - add_common_inputs: function() { - // add custom filters to this.inputs - this.custom_filters = new instance.web.search.CustomFilters(this); - // add Filters to this.inputs, need view.controls filled - (new instance.web.search.Filters(this)); - (new instance.web.search.SaveFilter(this, this.custom_filters)); - // add Advanced to this.inputs - (new instance.web.search.Advanced(this)); - }, - }); /** @@ -933,91 +723,17 @@ instance.web.search.fields = new instance.web.Registry({ 'many2many': 'instance.web.search.CharField', 'one2many': 'instance.web.search.CharField' }); -instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web.search.Invalid# */{ - /** - * Exception thrown by search widgets when they hold invalid values, - * which they can not return when asked. - * - * @constructs instance.web.search.Invalid - * @extends instance.web.Class - * - * @param field the name of the field holding an invalid value - * @param value the invalid value - * @param message validation failure message - */ - init: function (field, value, message) { - this.field = field; - this.value = value; - this.message = message; - }, - toString: function () { - return _.str.sprintf( - _t("Incorrect value for field %(fieldname)s: [%(value)s] is %(message)s"), - {fieldname: this.field, value: this.value, message: this.message} - ); - } -}); -instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{ - template: null, - /** - * Root class of all search widgets - * - * @constructs instance.web.search.Widget - * @extends instance.web.Widget - * - * @param parent parent of this widget - */ - init: function (parent) { - this._super(parent); - var ancestor = parent; - do { - this.drawer = ancestor; - } while (!(ancestor instanceof instance.web.SearchViewDrawer) - && (ancestor = (ancestor.getParent && ancestor.getParent()))); - this.view = this.drawer.searchview || this.drawer; - } -}); -instance.web.search.add_expand_listener = function($root) { - $root.find('a.searchview_group_string').click(function (e) { - $root.toggleClass('folded expanded'); - e.stopPropagation(); - e.preventDefault(); - }); -}; -instance.web.search.Group = instance.web.search.Widget.extend({ - init: function (parent, icon, node) { - this._super(parent); - var attrs = node.attrs; - this.modifiers = attrs.modifiers = - attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; - this.attrs = attrs; - this.icon = icon; - this.name = attrs.string; - this.children = []; - - this.drawer.controls.push(this); - }, - push: function (input) { - this.children.push(input); - }, - visible: function () { - return !this.modifiers.invisible; - }, -}); - -instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ - _in_drawer: false, +instance.web.search.Input = instance.web.Widget.extend( /** @lends instance.web.search.Input# */{ /** * @constructs instance.web.search.Input - * @extends instance.web.search.Widget + * @extends instance.web.Widget * * @param parent */ init: function (parent) { this._super(parent); this.load_attrs({}); - this.drawer.inputs.push(this); }, /** * Fetch auto-completion values for the widget. @@ -1049,9 +765,6 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan } return this.facet_for(defaults[this.attrs.name]); }, - in_drawer: function () { - return !!this._in_drawer; - }, get_context: function () { throw new Error( "get_context not implemented for widget " + this.attrs.type); @@ -1076,23 +789,22 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan * @returns {Boolean} */ visible: function () { - if (this.attrs.modifiers.invisible) { - return false; - } - var parent = this; - while ((parent = parent.getParent()) && - ( (parent instanceof instance.web.search.Group) - || (parent instanceof instance.web.search.Input))) { - if (!parent.visible()) { - return false; - } - } - return true; + return !this.attrs.modifiers.invisible; + + // var parent = this; + // while ((parent = parent.getParent()) && + // ( (parent instanceof instance.web.search.Group) + // || (parent instanceof instance.web.search.Input))) { + // if (!parent.visible()) { + // return false; + // } + // } + // return true; }, }); instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ template: 'SearchView.filters', - icon: 'q', + icon: "fa-filter", completion_label: _lt("Filter on: %s"), /** * Inclusive group of filters, creates a continuous "button" with clickable @@ -1116,10 +828,11 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in } this._super(parent); this.filters = filters; - this.view.query.on('add remove change reset', this.proxy('search_change')); + this.searchview = parent; + this.searchview.query.on('add remove change reset', this.proxy('search_change')); }, start: function () { - this.$el.on('click', 'li', this.proxy('toggle_filter')); + this.$el.on('click', 'a', this.proxy('toggle_filter')); return $.when(null); }, /** @@ -1128,15 +841,15 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in */ search_change: function () { var self = this; - var $filters = this.$('> li').removeClass('badge'); - var facet = this.view.query.find(_.bind(this.match_facet, this)); + var $filters = this.$el.removeClass('selected'); + var facet = this.searchview.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { var i = _(self.filters).indexOf(v.get('value')); if (i === -1) { return; } $filters.filter(function () { return Number($(this).data('index')) === i; - }).addClass('badge'); + }).addClass('selected'); }); }, /** @@ -1226,10 +939,11 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in }); }, toggle_filter: function (e) { - this.toggle(this.filters[Number($(e.target).data('index'))]); + e.stopPropagation(); + this.toggle(this.filters[Number($(e.target).parent().data('index'))]); }, - toggle: function (filter) { - this.view.query.toggle(this.make_facet([this.make_value(filter)])); + toggle: function (filter, options) { + this.searchview.query.toggle(this.make_facet([this.make_value(filter)]), options); }, complete: function (item) { var self = this; @@ -1260,10 +974,11 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in } }); instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ - icon: 'w', + icon: 'fa-bars', completion_label: _lt("Group by: %s"), init: function (filters, parent) { this._super(filters, parent); + this.searchview = parent; // Not flanders: facet unicity is handled through the // (category, field) pair of facet attributes. This is all well and // good for regular filter groups where a group matches a facet, but for @@ -1271,8 +986,8 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ // view which proxies to the first GroupbyGroup, so it can be used // for every GroupbyGroup and still provides the various methods needed // by the search view. Use weirdo name to avoid risks of conflicts - if (!this.view._s_groupby) { - this.view._s_groupby = { + if (!this.searchview._s_groupby) { + this.searchview._s_groupby = { help: "See GroupbyGroup#init", get_context: this.proxy('get_context'), get_domain: this.proxy('get_domain'), @@ -1281,14 +996,14 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ } }, match_facet: function (facet) { - return facet.get('field') === this.view._s_groupby; + return facet.get('field') === this.searchview._s_groupby; }, make_facet: function (values) { return { category: _t("GroupBy"), icon: this.icon, values: values, - field: this.view._s_groupby + field: this.searchview._s_groupby }; } }); @@ -1317,14 +1032,6 @@ instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instanc get_domain: function () { }, }); -instance.web.search.Separator = instance.web.search.Input.extend({ - _in_drawer: false, - - complete: function () { - return {is_separator: true}; - } -}); - instance.web.search.Field = instance.web.search.Input.extend( /** @lends instance.web.search.Field# */ { template: 'SearchView.field', default_operator: '=', @@ -1499,7 +1206,7 @@ instance.web.search.FloatField = instance.web.search.NumberField.extend(/** @len function facet_from(field, pair) { return { field: field, - category: field['attrs'].string, + category: field.attrs.string, values: [{label: pair[1], value: pair[0]}] }; } @@ -1614,6 +1321,7 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ init: function (view_section, field, parent) { this._super(view_section, field, parent); this.model = new instance.web.Model(this.attrs.relation); + this.searchview = parent; }, complete: function (value) { @@ -1637,7 +1345,7 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ var self = this; // FIXME: "concurrent" searches (multiple requests, mis-ordered responses) var context = instance.web.pyeval.eval( - 'contexts', [this.view.dataset.get_context()]); + 'contexts', [this.searchview.dataset.get_context()]); return this.model.call('name_search', [], { name: needle, args: (typeof this.attrs.domain === 'string') ? [] : this.attrs.domain, @@ -1697,40 +1405,286 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ } }); -instance.web.search.CustomFilters = instance.web.search.Input.extend({ - template: 'SearchView.Custom', - _in_drawer: true, - init: function () { - this.is_ready = $.Deferred(); - this._super.apply(this,arguments); +instance.web.search.FilterMenu = instance.web.Widget.extend({ + template: 'SearchView.FilterMenu', + events: { + 'click .oe-add-filter': function () { + this.toggle_custom_filter_menu(); + }, + 'click li': function (event) {event.stopImmediatePropagation();}, + 'hidden.bs.dropdown': function () { + this.toggle_custom_filter_menu(false); + }, + 'click .oe-add-condition': 'append_proposition', + 'click .oe-apply-filter': 'commit_search', + }, + init: function (parent, filters, fields_def) { + var self = this; + this._super(parent); + this.filters = filters || []; + this.searchview = parent; + this.propositions = []; + this.fields_def = fields_def.then(function (data) { + var fields = { + id: { string: 'ID', type: 'id', searchable: true } + }; + _.each(data, function(field_def, field_name) { + if (field_def.selectable !== false && field_name !== 'id') { + fields[field_name] = field_def; + } + }); + return fields; + }); }, start: function () { var self = this; + this.$menu = this.$('.filters-menu'); + this.$add_filter = this.$('.oe-add-filter'); + this.$apply_filter = this.$('.oe-apply-filter'); + this.$add_filter_menu = this.$('.oe-add-filter-menu'); + _.each(this.filters, function (group) { + group.insertBefore(self.$add_filter); + $('
  • ').addClass('divider').insertBefore(self.$add_filter); + }); + this.append_proposition().then(function (prop) { + prop.$el.hide(); + }); + }, + toggle_custom_filter_menu: function (is_open) { + this.$add_filter + .toggleClass('closed-menu', !is_open) + .toggleClass('open-menu', is_open); + this.$add_filter_menu.toggle(is_open); + if (this.$add_filter.hasClass('closed-menu') && (!this.propositions.length)) { + this.append_proposition(); + } + this.$('.oe-filter-condition').toggle(is_open); + }, + append_proposition: function () { + var self = this; + return this.fields_def.then(function (fields) { + var prop = new instance.web.search.ExtendedSearchProposition(self, fields); + self.propositions.push(prop); + prop.insertBefore(self.$add_filter_menu); + self.$apply_filter.prop('disabled', false); + return prop; + }); + }, + remove_proposition: function (prop) { + this.propositions = _.reject(this.propositions, function (p) { return p === prop}); + if (!this.propositions.length) { + this.$apply_filter.prop('disabled', true); + } + prop.destroy(); + }, + commit_search: function () { + var self = this, + filters = _.invoke(this.propositions, 'get_filter'), + filters_widgets = _.map(filters, function (filter) { + return new my.Filter(filter, self); + }); + var filter_group = new my.FilterGroup(filters_widgets, this.searchview); + filter_group.insertBefore(this.$add_filter); + $('
  • ').addClass('divider').insertBefore(self.$add_filter); + + filters_widgets.forEach(function (filter) { + self.searchview.query.add(filter_group.make_facet([filter_group.make_value(filter)]), {silent: true}); + }); + this.searchview.query.trigger('reset'); + + _.invoke(this.propositions, 'destroy'); + this.propositions = []; + this.append_proposition(); + this.toggle_custom_filter_menu(false); + }, +}); + +instance.web.search.GroupByMenu = instance.web.Widget.extend({ + template: 'SearchView.GroupByMenu', + events: { + 'click li': function (event) { + event.stopImmediatePropagation(); + }, + 'hidden.bs.dropdown': function () { + this.toggle_add_menu(false); + }, + 'click .add-custom-group a': function () { + this.toggle_add_menu(); + }, + }, + init: function (parent, groups, fields_def) { + this._super(parent); + this.groups = groups || []; + this.groupable_fields = {}; + this.searchview = parent; + this.fields_def = fields_def.then(this.proxy('get_groupable_fields')); + }, + start: function () { + var self = this; + this.$menu = this.$('.group-by-menu'); + var divider = this.$menu.find('.divider'); + _.invoke(this.groups, 'insertBefore', divider); + if (this.groups.length) { + divider.show(); + } + this.$add_group = this.$menu.find('.add-custom-group'); + this.fields_def.then(function () { + self.$menu.append(QWeb.render('GroupByMenuSelector', self)); + self.$add_group_menu = self.$('.oe-add-group'); + self.$group_selector = self.$('.oe-group-selector'); + self.$('.oe-select-group').click(function (event) { + self.toggle_add_menu(false); + var field = self.$group_selector.find(':selected').data('name'); + self.add_groupby_to_menu(field); + }); + }); + }, + get_groupable_fields: function (fields) { + var self = this, + groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime']; + + _.each(fields, function (field, name) { + if (field.store && _.contains(groupable_types, field.type)) { + self.groupable_fields[name] = field; + } + }); + }, + toggle_add_menu: function (is_open) { + this.$add_group + .toggleClass('closed-menu', !is_open) + .toggleClass('open-menu', is_open); + this.$add_group_menu.toggle(is_open); + if (this.$add_group.hasClass('open-menu')) { + this.$group_selector.focus(); + } + }, + add_groupby_to_menu: function (field_name) { + var filter = new my.Filter({attrs:{ + context:"{'group_by':'" + field_name + "''}", + name: this.groupable_fields[field_name].string, + }}, this.searchview); + var group = new my.FilterGroup([filter], this.searchview), + divider = this.$('.divider').show(); + group.insertBefore(divider); + group.toggle(filter); + }, +}); + +instance.web.search.FavoriteMenu = instance.web.Widget.extend({ + template: 'SearchView.FavoriteMenu', + events: { + 'click li': function (event) { + event.stopImmediatePropagation(); + }, + 'click .oe-save-search a': function () { + this.toggle_save_menu(); + }, + 'click .oe-save-name button': 'save_favorite', + 'hidden.bs.dropdown': function () { + this.close_menus(); + }, + }, + init: function (parent, query, target_model) { + this._super.apply(this,arguments); + this.searchview = parent; + this.query = query; + this.target_model = target_model; this.model = new instance.web.Model('ir.filters'); this.filters = {}; this.$filters = {}; - this.view.query + var action = instance.client.action_manager.inner_action; + this.action_id = action && action.id; + }, + start: function () { + var self = this; + this.$save_search = this.$('.oe-save-search'); + this.$save_name = this.$('.oe-save-name'); + this.$inputs = this.$save_name.find('input'); + this.$divider = this.$('.divider'); + + var $shared_filter = $(this.$inputs[1]), + $default_filter = $(this.$inputs[2]); + $shared_filter.click(function () {$default_filter.prop('checked', false)}); + $default_filter.click(function () {$shared_filter.prop('checked', false)}); + + this.query .on('remove', function (facet) { - if (!facet.get('is_custom_filter')) { - return; + if (facet.get('is_custom_filter')) { + self.clear_selection(); } - self.clear_selection(); }) .on('reset', this.proxy('clear_selection')); - return this.model.call('get_filters', [this.view.model, this.get_action_id()]) - .then(this.proxy('set_filters')) - .done(function () { self.is_ready.resolve(); }) - .fail(function () { self.is_ready.reject.apply(self.is_ready, arguments); }); + return this.model.call('get_filters', [this.target_model, this.action_id]) + .done(this.proxy('prepare_dropdown_menu')); }, - get_action_id: function(){ - var action = instance.client.action_manager.inner_action; - if (action) return action.id; + prepare_dropdown_menu: function (filters) { + filters.map(this.append_filter.bind(this)); }, - /** - * Special implementation delaying defaults until CustomFilters is loaded - */ - facet_for_defaults: function () { - return this.is_ready; + toggle_save_menu: function (is_open) { + this.$save_search + .toggleClass('closed-menu', !is_open) + .toggleClass('open-menu', is_open); + this.$save_name.toggle(is_open); + if (this.$save_search.hasClass('open-menu')) { + this.$save_name.find('input').first().focus(); + } + }, + close_menus: function () { + this.toggle_save_menu(false); + }, + save_favorite: function () { + var self = this, + filter_name = this.$inputs[0].value, + shared_filter = this.$inputs[1].checked, + default_filter = this.$inputs[2].checked; + if (!filter_name.length){ + this.do_warn(_t("Error"), _t("Filter name is required.")); + this.$inputs.first().focus(); + return; + } + var search = this.searchview.build_search_data(), + results = instance.web.pyeval.sync_eval_domains_and_contexts({ + domains: search.domains, + contexts: search.contexts, + group_by_seq: search.groupbys || [], + }); + if (!_.isEmpty(results.group_by)) { + results.context.group_by = results.group_by; + } + // Don't save user_context keys in the custom filter, otherwise end + // up with e.g. wrong uid or lang stored *and used in subsequent + // reqs* + var ctx = results.context; + _(_.keys(instance.session.user_context)).each(function (key) { + delete ctx[key]; + }); + var filter = { + name: filter_name, + user_id: shared_filter ? false : instance.session.uid, + model_id: this.searchview.dataset.model, + context: results.context, + domain: results.domain, + is_default: default_filter, + action_id: this.action_id, + }; + return this.model.call('create_or_replace', [filter]).done(function (id) { + filter.id = id; + self.toggle_save_menu(false); + self.$save_name.find('input').val('').prop('checked', false); + self.append_filter(filter); + self.toggle_filter(filter, true); + }); + }, + get_default_filter: function () { + var personal_filter = _.find(this.filters, function (filter) { + return filter.user_id && filter.is_default; + }); + if (personal_filter) { + return personal_filter; + } + return _.find(this.filters, function (filter) { + return !filter.user_id && filter.is_default; + }); }, /** * Generates a mapping key (in the filters and $filter mappings) for the @@ -1746,9 +1700,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ */ key_for: function (filter) { var user_id = filter.user_id, - action_id = filter.action_id; - var uid = (user_id instanceof Array) ? user_id[0] : user_id; - var act_id = (action_id instanceof Array) ? action_id[0] : action_id; + action_id = filter.action_id, + uid = (user_id instanceof Array) ? user_id[0] : user_id, + act_id = (action_id instanceof Array) ? action_id[0] : action_id; return _.str.sprintf('(%s)(%s)%s', uid, act_id, filter.name); }, /** @@ -1764,264 +1718,82 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ facet_for: function (filter) { return { category: _t("Custom Filter"), - icon: 'M', + icon: 'fa-star', field: { get_context: function () { return filter.context; }, get_groupby: function () { return [filter.context]; }, get_domain: function () { return filter.domain; } }, - _id: filter['id'], + _id: filter.id, is_custom_filter: true, values: [{label: filter.name, value: null}] }; }, clear_selection: function () { - this.$('span.badge').removeClass('badge'); + this.$('li.selected').removeClass('selected'); }, append_filter: function (filter) { - var self = this; - var key = this.key_for(filter); - var warning = _t("This filter is global and will be removed for everybody if you continue."); + var self = this, + key = this.key_for(filter), + $filter, + warning = _t("This filter is global and will be removed for everybody if you continue."); - var $filter; + this.$divider.show(); if (key in this.$filters) { $filter = this.$filters[key]; } else { - var id = filter.id; this.filters[key] = filter; $filter = $('
  • ') - .appendTo(this.$('.oe_searchview_custom_list')) + .insertBefore(this.$divider) .toggleClass('oe_searchview_custom_default', filter.is_default) - .append(this.$filters[key] = $('').text(filter.name)); + .append($('
    ').text(filter.name)); + this.$filters[key] = $filter; this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private' : 'oe_searchview_custom_public') - - $('x') - .click(function (e) { - e.stopPropagation(); - if (!(filter.user_id || confirm(warning))) { - return; - } - self.model.call('unlink', [id]).done(function () { - $filter.remove(); - delete self.$filters[key]; - delete self.filters[key]; - if (_.isEmpty(self.filters)) { - self.hide(); - } - }); + $('') + .addClass('fa fa-trash-o') + .click(function (event) { + event.stopImmediatePropagation(); + self.remove_filter(filter, $filter, key); }) .appendTo($filter); } - - this.$filters[key].unbind('click').click(function () { + this.$filters[key].unbind('click').click(function (event) { + event.stopPropagation(); self.toggle_filter(filter); }); - this.show(); }, toggle_filter: function (filter, preventSearch) { - var current = this.view.query.find(function (facet) { + var current = this.query.find(function (facet) { return facet.get('_id') === filter.id; }); if (current) { - this.view.query.remove(current); - this.$filters[this.key_for(filter)].removeClass('badge'); + this.query.remove(current); + this.$filters[this.key_for(filter)].removeClass('selected'); return; } - this.view.query.reset([this.facet_for(filter)], { + this.query.reset([this.facet_for(filter)], { preventSearch: preventSearch || false}); - this.$filters[this.key_for(filter)].addClass('badge'); + this.$filters[this.key_for(filter)].addClass('selected'); }, - set_filters: function (filters) { - _(filters).map(_.bind(this.append_filter, this)); - if (!filters.length) { - this.hide(); - } - }, - hide: function () { - this.$el.hide(); - }, - show: function () { - this.$el.show(); - }, -}); - -instance.web.search.SaveFilter = instance.web.search.Input.extend({ - template: 'SearchView.SaveFilter', - _in_drawer: true, - init: function (parent, custom_filters) { - this._super(parent); - this.custom_filters = custom_filters; - }, - start: function () { + remove_filter: function (filter, $filter, key) { var self = this; - this.model = new instance.web.Model('ir.filters'); - this.$el.on('submit', 'form', this.proxy('save_current')); - this.$el.on('click', 'input[type=checkbox]', function() { - $(this).siblings('input[type=checkbox]').prop('checked', false); - }); - this.$el.on('click', 'h4', function () { - self.$el.toggleClass('oe_opened'); - }); - }, - save_current: function () { - var self = this; - var $name = this.$('input:first'); - var private_filter = !this.$('#oe_searchview_custom_public').prop('checked'); - var set_as_default = this.$('#oe_searchview_custom_default').prop('checked'); - if (_.isEmpty($name.val())){ - this.do_warn(_t("Error"), _t("Filter name is required.")); - return false; + var warning = _t("This filter is global and will be removed for everybody if you continue."); + if (!(filter.user_id || confirm(warning))) { + return; } - var search = this.view.build_search_data(); - instance.web.pyeval.eval_domains_and_contexts({ - domains: search.domains, - contexts: search.contexts, - group_by_seq: search.groupbys || [] - }).done(function (results) { - if (!_.isEmpty(results.group_by)) { - results.context.group_by = results.group_by; + this.model.call('unlink', [filter.id]).done(function () { + $filter.remove(); + delete self.$filters[key]; + delete self.filters[key]; + if (_.isEmpty(self.filters)) { + self.$('li.divider').remove(); } - // Don't save user_context keys in the custom filter, otherwise end - // up with e.g. wrong uid or lang stored *and used in subsequent - // reqs* - var ctx = results.context; - _(_.keys(instance.session.user_context)).each(function (key) { - delete ctx[key]; - }); - var filter = { - name: $name.val(), - user_id: private_filter ? instance.session.uid : false, - model_id: self.view.model, - context: results.context, - domain: results.domain, - is_default: set_as_default, - action_id: self.custom_filters.get_action_id() - }; - // FIXME: current context? - return self.model.call('create_or_replace', [filter]).done(function (id) { - filter.id = id; - if (self.custom_filters) { - self.custom_filters.append_filter(filter); - } - self.$el - .removeClass('oe_opened') - .find('form')[0].reset(); - }); - }); - return false; + }); }, }); -instance.web.search.Filters = instance.web.search.Input.extend({ - template: 'SearchView.Filters', - _in_drawer: true, - start: function () { - var self = this; - var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; - var visible_filters = _(this.drawer.controls).chain().reject(function (group) { - return _(_(group.children).filter(is_group)).isEmpty() - || group.modifiers.invisible; - }); - - var groups = visible_filters.map(function (group) { - var filters = _(group.children).filter(is_group); - return { - name: _.str.sprintf("%s %s", - group.icon, group.name), - filters: filters, - length: _(filters).chain().map(function (i) { - return i.filters.length; }).sum().value() - }; - }).value(); - - var $dl = $('
    ').appendTo(this.$el); - - var rendered_lines = _.map(groups, function (group) { - $('
    ').html(group.name).appendTo($dl); - var $dd = $('
    ').appendTo($dl); - return $.when.apply(null, _(group.filters).invoke('appendTo', $dd)); - }); - - return $.when.apply(this, rendered_lines); - }, -}); - -instance.web.search.Advanced = instance.web.search.Input.extend({ - template: 'SearchView.advanced', - _in_drawer: true, - start: function () { - var self = this; - this.$el - .on('keypress keydown keyup', function (e) { e.stopPropagation(); }) - .on('click', 'h4', function () { - self.$el.toggleClass('oe_opened'); - }).on('click', 'button.oe_add_condition', function () { - self.append_proposition(); - }).on('submit', 'form', function (e) { - e.preventDefault(); - self.commit_search(); - }); - return $.when( - this._super(), - new instance.web.Model(this.view.model).call('fields_get', { - context: this.view.dataset.context - }).done(function(data) { - self.fields = { - id: { string: 'ID', type: 'id', searchable: true } - }; - _.each(data, function(field_def, field_name) { - if (field_def.selectable !== false && field_name != 'id') { - self.fields[field_name] = field_def; - } - }); - })).done(function () { - self.append_proposition(); - }); - }, - append_proposition: function () { - var self = this; - return (new instance.web.search.ExtendedSearchProposition(this, this.fields)) - .appendTo(this.$('ul')).done(function () { - self.$('button.oe_apply').prop('disabled', false); - }); - }, - remove_proposition: function (prop) { - // removing last proposition, disable apply button - if (this.getChildren().length <= 1) { - this.$('button.oe_apply').prop('disabled', true); - } - prop.destroy(); - }, - commit_search: function () { - // Get domain sections from all propositions - var children = this.getChildren(); - var propositions = _.invoke(children, 'get_proposition'); - var domain = _(propositions).pluck('value'); - for (var i = domain.length; --i;) { - domain.unshift('|'); - } - - this.view.query.add({ - category: _t("Advanced"), - values: propositions, - field: { - get_context: function () { }, - get_domain: function () { return domain;}, - get_groupby: function () { } - } - }); - - // remove all propositions - _.invoke(children, 'destroy'); - // add new empty proposition - this.append_proposition(); - // TODO: API on searchview - this.view.$el.removeClass('oe_searchview_open_drawer'); - } -}); - instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ template: 'SearchView.extended_search.proposition', events: { @@ -2030,7 +1802,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ 'click .searchview_extended_delete_prop': function (e) { e.stopPropagation(); this.getParent().remove_proposition(this); - } + }, }, /** * @constructs instance.web.search.ExtendedSearchProposition @@ -2101,18 +1873,22 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ this.value.appendTo($value_loc); }, - get_proposition: function() { + get_filter: function () { if (this.attrs.selected === null || this.attrs.selected === undefined) return null; - var field = this.attrs.selected; - var op_select = this.$('.searchview_extended_prop_op')[0]; - var operator = op_select.options[op_select.selectedIndex]; + var field = this.attrs.selected, + op_select = this.$('.searchview_extended_prop_op')[0], + operator = op_select.options[op_select.selectedIndex]; return { - label: this.value.get_label(field, operator), - value: this.value.get_domain(field, operator), + attrs: { + domain: [this.value.get_domain(field, operator)], + string: this.value.get_label(field, operator), + }, + children: [], + tag: 'filter', }; - } + }, }); instance.web.search.ExtendedSearchProposition.Field = instance.web.Widget.extend({ @@ -2337,7 +2113,6 @@ instance.web.search.AutoComplete = instance.web.Widget.extend({ this.delay = options.delay; this.select = options.select, this.get_search_string = options.get_search_string; - this.width = options.width || 400; this.current_result = null; @@ -2347,7 +2122,6 @@ instance.web.search.AutoComplete = instance.web.Widget.extend({ }, start: function () { var self = this; - this.$el.width(this.width); this.$input.on('keyup', function (ev) { if (ev.which === $.ui.keyCode.RIGHT) { self.searching = true; diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 88e62433adb..2659d46772f 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -19,9 +19,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ this.dialog = null; this.dialog_widget = null; this.view_managers = []; - this.on('history_back', this, function() { - return this.history_back(); - }); + this.on('history_back', this, this.proxy('history_back')); }, dialog_stop: function (reason) { if (this.dialog) { @@ -53,11 +51,20 @@ instance.web.ActionManager = instance.web.Widget.extend({ var to_destroy = this.view_managers; this.view_managers = []; } - this.view_managers.push(widget); + if (widget instanceof instance.web.ViewManager) { + this.view_managers.push(widget); + } else { + this.view_managers.push({ + view_stack: [{ + controller: {get: function () {return action.display_name || action.name; }}, + }], + destroy: function () {}, + }); + } this.inner_action = action; this.inner_widget = widget; return $.when(this.inner_widget.appendTo(this.$el)).done(function () { - widget.$header && widget.$header.show(); + (action.target !== 'inline') && (!action.flags.headless) && widget.$header && widget.$header.show(); old_widget && old_widget.$el.hide(); if (clear_breadcrumbs) { self.clear_view_managers(to_destroy) @@ -76,33 +83,32 @@ instance.web.ActionManager = instance.web.Widget.extend({ }), true); }, 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); - // } + var view_manager = _.last(this.view_managers), + nbr_views = view_manager.view_stack.length; + if (nbr_views > 1) { + this.select_view_manager(view_manager, nbr_views - 2); + } else if (this.view_managers.length > 1) { + view_manager = this.view_managers[this.view_managers.length -2]; + nbr_views = view_manager.view_stack.length; + this.select_view(view_managers, nbr_views - 2) + } }, select_view_manager: function(view_manager, index) { var self = this; + if (this.webclient.has_uncommitted_changes()) { + return false; + } var vm_index = this.view_managers.indexOf(view_manager); - view_manager.select_view(index).done(function () { - _.each(self.view_managers.splice(vm_index + 1), function (vm) { - vm.destroy(); + if (view_manager.select_view) { + view_manager.select_view(index).done(function () { + _.each(self.view_managers.splice(vm_index + 1), function (vm) { + vm.destroy(); + }); + self.inner_widget = _.last(self.view_managers); + self.inner_widget.display_breadcrumbs(); + self.inner_widget.$el.show(); }); - self.inner_widget = _.last(self.view_managers); - self.inner_widget.display_breadcrumbs(); - self.inner_widget.$el.show(); - }); + } }, clear_view_managers: function(vms) { _.each(vms || this.view_managers, function (vm) { @@ -127,7 +133,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ // }); }, do_push_state: function(state) { - if (!this.webclient || !this.webclient.do_push_state) { + if (!this.webclient || !this.webclient.do_push_state || this.dialog) { return; } state = state || {}; @@ -168,9 +174,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ } } } - if(!this.dialog) { - this.webclient.do_push_state(state); - } + this.webclient.do_push_state(state); }, do_load_state: function(state, warm) { var self = this, @@ -386,6 +390,9 @@ instance.web.ActionManager = instance.web.Widget.extend({ $buttons: this.dialog.$buttons, footer_to_buttons: true, }); + if (widget.action.view_mode === 'form') { + widget.flags.headless = true; + } } this.dialog_widget = widget; this.dialog_widget.setParent(this.dialog); @@ -398,9 +405,6 @@ instance.web.ActionManager = instance.web.Widget.extend({ } widget = executor.widget(); this.dialog_stop(executor.action); - if (options.clear_breadcrumbs) { - // this.clear_view_managers(); - } return this.push_view_manager(widget, executor.action, options.clear_breadcrumbs); }, ir_actions_act_window: function (action, options) { @@ -412,13 +416,6 @@ instance.web.ActionManager = instance.web.Widget.extend({ }, action: action, klass: 'oe_act_window', - // post_process: function (widget) { - // self.push_breadcrumb({ - // widget: widget, - // on_reverse_breadcrumb: options.on_reverse_breadcrumb, - // hide_breadcrumb: options.hide_breadcrumb, - // }); - // }, }, options); }, ir_actions_client: function (action, options) { @@ -440,18 +437,9 @@ instance.web.ActionManager = instance.web.Widget.extend({ widget: function () { return new ClientWidget(self, action); }, action: action, klass: 'oe_act_client', - // post_process: function(widget) { - // self.push_breadcrumb({ - // widget: widget, - // title: action.name, - // on_reverse_breadcrumb: options.on_reverse_breadcrumb, - // hide_breadcrumb: options.hide_breadcrumb, - // }); - // if (action.tag !== 'reload') { - // self.do_push_state({}); - // } - // } - }, options); + }, options).then(function () { + if (action.tag !== 'reload') {self.do_push_state({});} + }); }, ir_actions_act_window_close: function (action, options) { if (!this.dialog) { @@ -544,38 +532,46 @@ instance.web.ViewManager = instance.web.Widget.extend({ _.each(views, function (view) { var view_type = view[1] || view.view_type, View = instance.web.views.get_object(view_type, true), - view_label = View ? View.prototype.display_name : (void 'nope'); - self.view_order.push(view_type); - self.views[view_type] = { - controller: null, - options: view.options || {}, - view_id: view[0] || view.view_id, - type: view_type, - label: view_label, - embedded_view: view.embedded_view, - title: self.action && self.action.name, - button_label: View ? _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}) : (void 'nope'), - }; + view_label = View ? View.prototype.display_name: (void 'nope'), + view = { + controller: null, + options: view.options || {}, + view_id: view[0] || view.view_id, + type: view_type, + label: view_label, + embedded_view: view.embedded_view, + title: self.action && self.action.name, + button_label: View ? _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}) : (void 'nope'), + }; + self.view_order.push(view); + self.views[view_type] = view; }); + this.multiple_views = (self.view_order.length - ('form' in this.views ? 1 : 0)) > 1; }, /** * @returns {jQuery.Deferred} initial view loading promise */ start: function() { var self = this; - var default_view = this.flags.default_view || this.view_order[0], + var default_view = this.flags.default_view || this.view_order[0].type, default_options = this.flags[default_view] && this.flags[default_view].options; + if (this.flags.headless) { + this.$('.oe-view-manager-header').hide(); + } this._super(); - var $sidebar = this.flags.sidebar ? this.$('.oe_view_manager_sidebar') : undefined, + var $sidebar = this.flags.sidebar ? this.$('.oe-view-manager-sidebar') : undefined, $pager = this.$('.oe-view-manager-pager'); this.$breadcrumbs = this.$('.oe-view-title'); - this.$switch_buttons = this.$('.oe-view-manager-switch a'); + this.$switch_buttons = this.$('.oe-view-manager-switch button'); this.$header = this.$('.oe-view-manager-header'); - - self.$switch_buttons.click(function () { - self.switch_mode($(this).data('view-type')); + this.$header_col = this.$header.find('.oe-header-title'); + this.$search_col = this.$header.find('.oe-view-manager-search-view'); + this.$switch_buttons.click(function (event) { + if (!$(event.target).hasClass('active')) { + self.switch_mode($(this).data('view-type')); + } }); var views_ids = {}; _.each(this.views, function (view) { @@ -587,21 +583,29 @@ instance.web.ViewManager = instance.web.Widget.extend({ action : self.action, action_views_ids : views_ids, }, self.flags, self.flags[view.type], view.options); + if (view.type !== 'form') { + self.$('.oe-vm-switch-' + view.type).tooltip(); + } }); + this.$('.oe_debug_view').click(this.on_debug_changed); + this.$el.addClass("oe_view_manager_" + ((this.action && this.action.target) || 'current')); - var search_view_loaded = this.flags.search_view ? this.setup_search_view() : $.when(), - main_view_loaded = this.switch_mode(default_view, null, default_options); + this.search_view_loaded = this.setup_search_view(); + var main_view_loaded = this.switch_mode(default_view, null, default_options); - return $.when(main_view_loaded, search_view_loaded); + return $.when(main_view_loaded, this.search_view_loaded); }, switch_mode: function(view_type, no_store, view_options) { var self = this, view = this.views[view_type]; + if (!view) { + return $.Deferred().reject(); + } if (view_type !== 'form') { this.view_stack = []; - } + } this.view_stack.push(view); this.active_view = view; @@ -613,7 +617,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ if (this.searchview && this.flags.auto_search && view.controller.searchable !== false) { - $.when(this.searchview.ready, view.created).done(this.searchview.do_search); + $.when(this.search_view_loaded, view.created).done(this.searchview.do_search); } else { this.active_search.resolve(); } @@ -623,6 +627,12 @@ instance.web.ViewManager = instance.web.Widget.extend({ self.active_view = view; self._display_view(view.type, view_options); self.trigger('switch_mode', view_type, no_store, view_options); + if (self.session.debug) { + self.$('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { + view: self.active_view.controller, + view_manager: self, + })); + } }); }, update_header: function () { @@ -642,8 +652,10 @@ instance.web.ViewManager = instance.web.Widget.extend({ }); self.active_view.options.$buttons && self.active_view.options.$buttons.show(); if (self.searchview) { - var is_hidden = self.active_view.controller.searchable === false || self.searchview.options.hidden; + var is_hidden = self.active_view.controller.searchable === false; self.searchview.toggle_visibility(!is_hidden); + self.$header_col.toggleClass('col-md-6', !is_hidden).toggleClass('col-md-12', is_hidden); + self.$search_col.toggle(!is_hidden); } self.display_breadcrumbs(); }); @@ -667,7 +679,8 @@ instance.web.ViewManager = instance.web.Widget.extend({ create_view: function(view) { var self = this, View = this.registry.get_object(view.type), - options = _.clone(view.options); + options = _.clone(view.options), + view_loaded = $.Deferred(); if (view.type === "form" && this.action && (this.action.target === 'new' || this.action.target === 'inline')) { options.initial_mode = 'edit'; @@ -677,25 +690,26 @@ instance.web.ViewManager = instance.web.Widget.extend({ $container.hide(); view.controller = controller; + view.$container = $container; if (view.embedded_view) { controller.set_embedded_view(view.embedded_view); } controller.on('switch_mode', this, this.switch_mode.bind(this)); - - view.$container = $container; - - var view_loaded = $.Deferred(); - view.controller.on('view_loaded', this, function () { + controller.on('history_back', this, function () { + self.action_manager && self.action_manager.trigger('history_back'); + }); + controller.on("change:title", this, function() { + self.display_breadcrumbs(); + }); + controller.on('view_loaded', this, function () { view_loaded.resolve(); }); - return $.when(controller.appendTo($container), view_loaded) .done(function () { self.trigger("controller_inited", view.type, controller); }); }, - select_view: function (index) { var view_type = this.view_stack[index].type; this.view_stack.splice(index); @@ -713,6 +727,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ } var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; + var search_defaults = {}; var context = this.action ? this.action.context : []; @@ -727,13 +742,13 @@ instance.web.ViewManager = instance.web.Widget.extend({ var options = { hidden: this.flags.search_view === false, disable_custom_filters: this.flags.search_disable_custom_filters, + $buttons: this.$('.oe-search-options'), }; var SearchView = instance.web.SearchView; this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options); this.searchview.on('search_data', this, this.search.bind(this)); - return this.searchview.appendTo(this.$(".oe-view-manager-search-view:first"), - this.$('.oe_searchview_drawer_container')); + return this.searchview.appendTo(this.$(".oe-view-manager-search-view:first")); }, search: function(domains, contexts, groupbys) { var self = this, @@ -783,6 +798,145 @@ instance.web.ViewManager = instance.web.Widget.extend({ self.active_view.controller.do_load_state(state, warm); }); }, + on_debug_changed: function (evt) { + var self = this, + params = $(evt.target).data(), + val = params.action, + current_view = this.active_view.controller; + switch (val) { + case 'fvg': + var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get") }).open(); + $('
    ').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el);
    +                break;
    +            case 'tests':
    +                this.do_action({
    +                    name: _t("JS Tests"),
    +                    target: 'new',
    +                    type : 'ir.actions.act_url',
    +                    url: '/web/tests?mod=*'
    +                });
    +                break;
    +            case 'get_metadata':
    +                var ids = current_view.get_selected_ids();
    +                if (ids.length === 1) {
    +                    this.dataset.call('get_metadata', [ids]).done(function(result) {
    +                        var dialog = new instance.web.Dialog(this, {
    +                            title: _.str.sprintf(_t("Metadata (%s)"), self.dataset.model),
    +                            size: 'medium',
    +                        }, QWeb.render('ViewManagerDebugViewLog', {
    +                            perm : result[0],
    +                            format : instance.web.format_value
    +                        })).open();
    +                    });
    +                }
    +                break;
    +            case 'toggle_layout_outline':
    +                current_view.rendering_engine.toggle_layout_debugging();
    +                break;
    +            case 'set_defaults':
    +                current_view.open_defaults_dialog();
    +                break;
    +            case 'translate':
    +                this.do_action({
    +                    name: _t("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('fields_get', [false, {}]).done(function (fields) {
    +                    var $root = $('
    '); + _(fields).each(function (attributes, name) { + $root.append($('
    ').append($('

    ').text(name))); + var $attrs = $('
    ').appendTo($('
    ').appendTo($root)); + _(attributes).each(function (def, name) { + if (def instanceof Object) { + def = JSON.stringify(def); + } + $attrs + .append($('
    ').text(name)) + .append($('
    ').text(def)); + }); + }); + new instance.web.Dialog(self, { + title: _.str.sprintf(_t("Model %s fields"), + self.dataset.model), + }, $root).open(); + }); + break; + case 'edit_workflow': + return this.do_action({ + res_model : 'workflow', + name: _t('Edit Workflow'), + domain : [['osv', '=', this.dataset.model]], + views: [[false, 'list'], [false, 'form'], [false, 'diagram']], + type : 'ir.actions.act_window', + view_type : 'list', + view_mode : 'list' + }); + case 'edit': + this.do_edit_resource(params.model, params.id, evt.target.text); + break; + case 'manage_filters': + this.do_action({ + res_model: 'ir.filters', + name: _t('Manage Filters'), + views: [[false, 'list'], [false, 'form']], + type: 'ir.actions.act_window', + context: { + search_default_my_filters: true, + search_default_model_id: this.dataset.model + } + }); + 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; + case 'leave_debug': + window.location.search="?"; + break; + default: + if (val) { + console.warn("No debug handler for ", val); + } + } + }, + do_edit_resource: function(model, id, name) { + this.do_action({ + res_model : model, + res_id : id, + name: name, + type : 'ir.actions.act_window', + view_type : 'form', + view_mode : 'form', + views : [[false, 'form']], + target : 'new', + flags : { + action_buttons : true, + headless: true, + } + }); + }, }); instance.web.ViewManagerAction = instance.web.ViewManager.extend({ @@ -793,9 +947,25 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ if (!('auto_search' in flags)) { flags.auto_search = action.auto_search !== false; } + if (action.res_model === 'board.board' && action.view_mode === 'form') { + action.target = 'inline'; + // Special case for Dashboards + _.extend(flags, { + views_switcher : false, + display_title : false, + search_view : false, + pager : false, + sidebar : false, + action_buttons : false + }); + } this.action = action; this.action_manager = parent; var dataset = new instance.web.DataSetSearch(this, action.res_model, action.context, action.domain); + if (action.res_id) { + dataset.ids.push(action.res_id); + dataset.index = 0; + } this._super(parent, dataset, action.views, flags); } }); @@ -824,7 +994,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ var self = this; this._super(this); this.redraw(); - this.$el.on('click','.oe_dropdown_menu li a', function(event) { + this.$el.on('click','.dropdown-menu li a', function(event) { var section = $(this).data('section'); var index = $(this).data('index'); var item = self.items[section][index]; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index e27524cc2c8..c45278196eb 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -25,13 +25,6 @@

    - - [ - -
    - -
    -

  • - - - - - + + x + +
  • + + + /web/binary/upload_attachment + + + + Add... + +
  • + + + + -

    (Any existing filter with the same name will be replaced)

    - -
    - - - - - -
    - -
    -
    - - - -
    - - - -
    - -
    - -
    - -
    -
    - -
    - -
    - - - -
    - -
    -
    +
    + + +
    + +
    + + +
    + + +
  • +
  • -
    - -
    - - -
    -
    -
    M Favorites
    -
      -
      -
      - -
      -

      Save current filter

      -
      -

      -

      - - - - -

      - -
      -
      - -
      -

      Advanced Search

      -
      -
        - -
      -
      - -
      +
      + +
      -
    • - or - - + + + + + + + + - + - + - From 524543fa3b13b6baf14178463becf7aa7ae250d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 7 Oct 2014 16:29:28 +0200 Subject: [PATCH 06/29] [FIX] make add to dashboard work With the search view refactoring, the add to dashboard functionality was unavailable. Weep no longer, it is fixed. The dashboard button is now an action in the favorites submenu instead of a search input --- addons/board/static/src/css/dashboard.css | 22 ++- addons/board/static/src/css/dashboard.sass | 33 +++-- addons/board/static/src/js/dashboard.js | 157 +++++++++------------ addons/board/static/src/xml/board.xml | 18 +-- 4 files changed, 105 insertions(+), 125 deletions(-) diff --git a/addons/board/static/src/css/dashboard.css b/addons/board/static/src/css/dashboard.css index f2bbcd07071..02304107fc2 100644 --- a/addons/board/static/src/css/dashboard.css +++ b/addons/board/static/src/css/dashboard.css @@ -1,3 +1,4 @@ +@charset "UTF-8"; .openerp .oe_dashboard_layout_1 .oe_dashboard_column.index_0 { width: 100%; } @@ -82,7 +83,7 @@ color: #b3b3b3; } .openerp .oe_dashboard .oe_action .oe_header .oe_icon:hover { - color: #666666; + color: #666; text-decoration: none; } .openerp .oe_dashboard .oe_action .oe_header .oe_close:after { @@ -116,8 +117,14 @@ .openerp .oe_dashboard .oe_action .oe_content { padding: 0 12px 12px 12px; } -.openerp .oe_dashboard .oe_action .oe_content .oe_view_manager_header { - display: none; +.openerp .oe_dashboard .oe_action .oe_content .oe-view-manager-views { + position: relative; +} +.openerp .oe_dashboard .oe_action .oe_content .oe-view-manager { + display: block; +} +.openerp .oe_dashboard .oe_action .oe_content .oe-view-manager-content { + display: block; } .openerp .oe_dashboard .oe_action .oe_content .oe_list_content > thead { border-bottom: 1px; @@ -125,3 +132,12 @@ .openerp .oe_dashboard .oe_action .oe_content .oe_list_content > tbody tr:nth-child(odd) { background: transparent; } +.openerp .oe-search-options .add-to-dashboard button { + margin: 3px 25px; + display: none; +} +.openerp .oe-search-options .add-to-dashboard input { + width: calc(100% - 50px); + margin: 3px 25px; + display: none; +} diff --git a/addons/board/static/src/css/dashboard.sass b/addons/board/static/src/css/dashboard.sass index 122b6dc232c..f412b1d3aa1 100644 --- a/addons/board/static/src/css/dashboard.sass +++ b/addons/board/static/src/css/dashboard.sass @@ -98,25 +98,14 @@ margin-left: 4px padding: 0 4px 0 4px height: 16px !important - //.oe_rename - //float: left - //padding-right: 4px - //position: relative - //top: 1px - //.oe_input - //height: 16px; - //position: relative; - //top: 2px; - //.ui-sortable-placeholder - //border: 1px dotted black; - //visibility: visible !important; - //height: 50px !important; - //* - //visibility: hidden; .oe_content padding: 0 12px 12px 12px - .oe_view_manager_header - display: none + .oe-view-manager-views + position: relative + .oe-view-manager + display: block + .oe-view-manager-content + display: block .oe_list_content > thead border-bottom: 1px @@ -124,3 +113,13 @@ tr:nth-child(odd) background: transparent + + .oe-search-options + .add-to-dashboard + button + margin: 3px 25px + display: none + input + width: calc(100% - 50px) + margin: 3px 25px + display: none diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js index 0d1d210a4de..5fa7811b5a0 100644 --- a/addons/board/static/src/js/dashboard.js +++ b/addons/board/static/src/js/dashboard.js @@ -18,7 +18,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ var self = this; this._super.apply(this, arguments); - this.$el.find('.oe_dashboard_column').sortable({ + this.$('.oe_dashboard_column').sortable({ connectWith: '.oe_dashboard_column', handle: '.oe_header', scroll: false @@ -29,9 +29,9 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ self.__parentedParent.set({ 'title': old_title}); }) // Events - this.$el.find('.oe_dashboard_link_reset').click(this.on_reset); - this.$el.find('.oe_dashboard_link_change_layout').click(this.on_change_layout); - this.$el.find('h2.oe_header span.oe_header_txt').click(function(ev){ + this.$('.oe_dashboard_link_reset').click(this.on_reset); + this.$('.oe_dashboard_link_change_layout').click(this.on_change_layout); + this.$('h2.oe_header span.oe_header_txt').click(function(ev){ if(ev.target === ev.currentTarget) self.on_header_string($(ev.target).parent()); }); @@ -208,6 +208,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ views_switcher : false, action_buttons : false, pager: false, + headless: true, low_profile: true, display_title: false, list: { @@ -244,7 +245,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ }; var list = am.inner_widget.views.list; if (list) { - list.deferred.done(function() { + list.created.done(function() { $(list.controller.groups).off('row_link').on('row_link', function(e, id) { new_form_action(id); }); @@ -252,7 +253,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ } var kanban = am.inner_widget.views.kanban; if (kanban) { - kanban.deferred.done(function() { + kanban.created.done(function() { kanban.controller.open_record = function(id, editable) { new_form_action(id, editable); }; @@ -326,113 +327,87 @@ instance.web.form.tags.add('vpaned', 'instance.web.form.DashBoardLegacy'); instance.web.form.tags.add('board', 'instance.web.form.DashBoard'); -instance.board.AddToDashboard = instance.web.search.Input.extend({ - template: 'SearchView.addtodashboard', - _in_drawer: true, - init: function (parent) { - this._super(parent); - this.is_loaded = null; - }, - start: function () { +instance.web.search.FavoriteMenu.include({ + prepare_dropdown_menu: function (filters) { var self = this; - this.$el - .on('click', 'h4', this.proxy('load_and_show_option')) - .on('submit', 'form', function (e) { - e.preventDefault(); - self.add_dashboard(); - }); - return $.when(); - }, - load_data:function(){ - var board = new instance.web.Model('board.board'); - return board.call('list', [board.context()]); - }, - _x:function() { - if (!instance.webclient) { return $.Deferred().reject(); } - var dashboard_menu = instance.webclient.menu.data.data.children; - return new instance.web.Model('ir.model.data') - .query(['res_id']) - .filter([['name','=','menu_reporting_dashboard']]) - .first().then(function (result) { - var menu = _(dashboard_menu).chain() - .pluck('children') - .flatten(true) - .find(function (child) { return child.id === result.res_id; }) - .value(); - return menu ? menu.children : []; + this._super(filters); + this.$('.favorites-menu').append(QWeb.render('SearchView.addtodashboard')); + this.$add_to_dashboard = this.$('.add-to-dashboard'); + this.$add_dashboard_btn = this.$add_to_dashboard.eq(2).find('button'); + this.$add_dashboard_input = this.$add_to_dashboard.eq(1).find('input'); + this.$add_dashboard_link = this.$add_to_dashboard.first().find('a'); + var title = this.searchview.getParent().title; + this.$add_dashboard_input.val(title); + this.$add_dashboard_link.click(function () { + self.toggle_dashboard_menu(); }); + this.$add_dashboard_btn.click(this.proxy('add_dashboard')); }, - render_data: function(dashboard_choices){ - var selection = instance.web.qweb.render( - "SearchView.addtodashboard.selection", { - selections: dashboard_choices}); - this.$("input").before(selection) + toggle_dashboard_menu: function (is_open) { + this.$add_to_dashboard.first() + .toggleClass('closed-menu', !is_open) + .toggleClass('open-menu', is_open); + this.$add_dashboard_btn.toggle(is_open); + this.$add_dashboard_input.toggle(is_open); + if (this.$add_to_dashboard.first().hasClass('open-menu')) { + this.$add_dashboard_input.focus(); + } }, - add_dashboard: function(){ + close_menus: function () { + this.toggle_dashboard_menu(false); + this._super(); + }, + add_dashboard: function () { var self = this; - if (! this.view.view_manager.action || ! this.$el.find("select").val()) { + var view_manager = this.getParent(); + while (!(view_manager instanceof instance.web.ViewManager) && + view_manager && view_manager.getParent) { + view_manager = view_manager.getParent(); + } + if (!view_manager.action) { this.do_warn(_t("Can't find dashboard action")); return; } - var data = this.view.build_search_data(); - var context = new instance.web.CompoundContext(this.view.dataset.get_context() || []); - var domain = new instance.web.CompoundDomain(this.view.dataset.get_domain() || []); + var searchview = view_manager.searchview, + data = searchview.build_search_data(), + context = new instance.web.CompoundContext(searchview.dataset.get_context() || []), + domain = new instance.web.CompoundDomain(searchview.dataset.get_domain() || []); _.each(data.contexts, context.add, context); _.each(data.domains, domain.add, domain); context.add({ group_by: instance.web.pyeval.eval('groupbys', data.groupbys || []) }); - var c = instance.web.pyeval.eval('context', context); for(var k in c) { if (c.hasOwnProperty(k) && /^search_default_/.test(k)) { delete c[k]; } } - // TODO: replace this 6.1 workaround by attribute on + this.toggle_dashboard_menu(false); c.dashboard_merge_domains_contexts = false; - var d = instance.web.pyeval.eval('domain', domain); - - this.rpc('/board/add_to_dashboard', { - menu_id: this.$el.find("select").val(), - action_id: this.view.view_manager.action.id, - context_to_save: c, - domain: d, - view_mode: this.view.view_manager.active_view, - name: this.$el.find("input").val() - }).done(function(r) { - if (r === false) { - self.do_warn(_t("Could not add filter to dashboard")); - } else { - self.$el.toggleClass('oe_opened'); - self.do_notify(_t("Filter added to dashboard"), ''); - } - }); + var d = instance.web.pyeval.eval('domain', domain), + board = new instance.web.Model('board.board'), + name = self.$add_dashboard_input.val(); + + board.call('list', [board.context()]) + .then(function (board_list) { + return self.rpc('/board/add_to_dashboard', { + menu_id: board_list[0].id, + action_id: view_manager.action.id, + context_to_save: c, + domain: d, + view_mode: view_manager.active_view.type, + name: name, + }); + }).then(function (r) { + if (r === false) { + self.do_warn(_t("Could not add filter to dashboard")); + } else { + self.do_notify(_t("'"+name + "' added to dashboard"), ''); + } + }); }, - load_and_show_option: function(){ - if (!this.is_loaded) { - this.is_loaded = this.load_data().done(this.proxy("render_data")); - } - this.is_loaded.done(this.proxy('show_option')); - }, - show_option: function () { - this.$el.toggleClass('oe_opened'); - if (! this.$el.hasClass('oe_opened')) - return; - this.$("input").val(this.view.fields_view.name || "" ); - }, -}); - - -instance.web.SearchViewDrawer.include({ - add_common_inputs: function() { - this._super(); - var vm = this.getParent().getParent(); - if (vm.inner_action && vm.inner_action.id && vm.inner_action.views) { - (new instance.board.AddToDashboard(this)); - } - } }); }; diff --git a/addons/board/static/src/xml/board.xml b/addons/board/static/src/xml/board.xml index 72f720e9b38..305799d0617 100644 --- a/addons/board/static/src/xml/board.xml +++ b/addons/board/static/src/xml/board.xml @@ -63,19 +63,9 @@