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); + $('
').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 = $('