Browse Source
[ADD] web_responsive: Create new module
[ADD] web_responsive: Create new module
* Create App Drawer for primary menu * Move secondary menu to top bar * Add keyboard shortcuts for primary menu navigation * Make web client slightly more mobile compatible (no widgets)pull/396/head
Dave Lasley
9 years ago
16 changed files with 3962 additions and 0 deletions
-
79web_app_drawer/README.rst
-
1web_app_drawer/__init__.py
-
21web_app_drawer/__openerp__.py
-
BINweb_app_drawer/static/description/icon.png
-
501web_app_drawer/static/lib/css/drawer.3.2.0.css
-
177web_app_drawer/static/lib/js/drawer.3.2.0.js
-
2148web_app_drawer/static/lib/js/iscroll-probe.5.2.0.js
-
292web_app_drawer/static/src/js/web_app_drawer.js
-
85web_app_drawer/static/src/less/app_drawer.less
-
13web_app_drawer/static/src/less/main.less
-
41web_app_drawer/static/src/less/navbar.less
-
15web_app_drawer/static/src/less/variables.less
-
291web_app_drawer/static/tests/js/web_app_drawer.js
-
16web_app_drawer/tests/test_ui.py
-
46web_app_drawer/views/assets.xml
-
236web_app_drawer/views/web.xml
@ -0,0 +1,79 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
============== |
||||
|
Web App Drawer |
||||
|
============== |
||||
|
|
||||
|
This module moves the side navigation panel to a top menu bar. It also provides |
||||
|
an app drawer that is mobile-compliant. |
||||
|
|
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
Keyboard Shortcuts |
||||
|
------------------ |
||||
|
|
||||
|
The following keyboard shortcuts are implemented: |
||||
|
|
||||
|
* Toggle App Drawer - `ActionKey <https://en.wikipedia.org/wiki/Access_key#Access_in_different_browsers`_+``A`` |
||||
|
* Navigate Apps Drawer - Arrow Keys |
||||
|
* Type to select App Links |
||||
|
* ``esc`` to close App Drawer |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/154/9.0 |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Provide keyboard navigation to secondary (top) menu |
||||
|
* Drag drawer from left to open in mobile |
||||
|
* Figure out how to test focus on hidden elements for keyboard nav tests |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues |
||||
|
<https://github.com/OCA/web/issues>`_. In case of trouble, please |
||||
|
check there if your issue has already been reported. If you spotted it first, |
||||
|
help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Images |
||||
|
------ |
||||
|
|
||||
|
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
* Jairo Llopis <jairo.llopis@tecnativa.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1 @@ |
|||||
|
# -*- coding: utf-8 -*- |
@ -0,0 +1,21 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
{ |
||||
|
"name": "Web App Drawer", |
||||
|
"summary": "It moves the sidebar to a main nav and adds an app drawer", |
||||
|
"version": "9.0.1.0.0", |
||||
|
"category": "Website", |
||||
|
"website": "https://laslabs.com/", |
||||
|
"author": "LasLabs, Odoo Community Association (OCA)", |
||||
|
"license": "LGPL-3", |
||||
|
"installable": True, |
||||
|
"depends": [ |
||||
|
'web', |
||||
|
], |
||||
|
"data": [ |
||||
|
'views/assets.xml', |
||||
|
'views/web.xml', |
||||
|
], |
||||
|
} |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,501 @@ |
|||||
|
/*! |
||||
|
* jquery-drawer v3.2.0 |
||||
|
* Flexible drawer menu using jQuery, iScroll and CSS. |
||||
|
* http://git.blivesta.com/drawer |
||||
|
* License : MIT |
||||
|
* Author : blivesta <design@blivesta.com> (http://blivesta.com/) |
||||
|
*/ |
||||
|
|
||||
|
/*!------------------------------------*\ |
||||
|
Base |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer-nav { |
||||
|
position: fixed; |
||||
|
z-index: 101; |
||||
|
top: 0; |
||||
|
overflow: hidden; |
||||
|
width: 16.25rem; |
||||
|
height: 100%; |
||||
|
color: #222; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
.drawer-brand { |
||||
|
font-size: 1.5rem; |
||||
|
font-weight: bold; |
||||
|
line-height: 3.75rem; |
||||
|
display: block; |
||||
|
padding-right: .75rem; |
||||
|
padding-left: .75rem; |
||||
|
text-decoration: none; |
||||
|
color: #222; |
||||
|
} |
||||
|
|
||||
|
.drawer-menu { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
list-style: none; |
||||
|
} |
||||
|
|
||||
|
.drawer-menu-item { |
||||
|
font-size: 1rem; |
||||
|
display: block; |
||||
|
padding: .75rem; |
||||
|
text-decoration: none; |
||||
|
color: #222; |
||||
|
} |
||||
|
|
||||
|
.drawer-menu-item:hover { |
||||
|
text-decoration: underline; |
||||
|
color: #555; |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
/*! overlay */ |
||||
|
.drawer-overlay { |
||||
|
position: fixed; |
||||
|
z-index: 100; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
display: none; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background-color: rgba(0, 0, 0, .2); |
||||
|
} |
||||
|
|
||||
|
.drawer-open .drawer-overlay { |
||||
|
display: block; |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Top |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer--top .drawer-nav { |
||||
|
top: -100%; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: auto; |
||||
|
max-height: 100%; |
||||
|
-webkit-transition: top .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
transition: top .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
} |
||||
|
|
||||
|
.drawer--top.drawer-open .drawer-nav { |
||||
|
top: 0; |
||||
|
} |
||||
|
|
||||
|
.drawer--top .drawer-hamburger, |
||||
|
.drawer--top.drawer-open .drawer-hamburger { |
||||
|
right: 0; |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Left |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer--left .drawer-nav { |
||||
|
left: -16.25rem; |
||||
|
-webkit-transition: left .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
transition: left .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
} |
||||
|
|
||||
|
.drawer--left.drawer-open .drawer-nav, |
||||
|
.drawer--left .drawer-hamburger, |
||||
|
.drawer--left.drawer-open .drawer-navbar .drawer-hamburger { |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.drawer--left.drawer-open .drawer-hamburger { |
||||
|
left: 16.25rem; |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Right |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer--right .drawer-nav { |
||||
|
right: -16.25rem; |
||||
|
-webkit-transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
} |
||||
|
|
||||
|
.drawer--right.drawer-open .drawer-nav, |
||||
|
.drawer--right .drawer-hamburger, |
||||
|
.drawer--right.drawer-open .drawer-navbar .drawer-hamburger { |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.drawer--right.drawer-open .drawer-hamburger { |
||||
|
right: 16.25rem; |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Hamburger |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer-hamburger { |
||||
|
position: fixed; |
||||
|
z-index: 104; |
||||
|
top: 0; |
||||
|
display: block; |
||||
|
box-sizing: content-box; |
||||
|
width: 2rem; |
||||
|
padding: 0; |
||||
|
padding-top: 18px; |
||||
|
padding-right: .75rem; |
||||
|
padding-bottom: 30px; |
||||
|
padding-left: .75rem; |
||||
|
-webkit-transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
-webkit-transform: translate3d(0, 0, 0); |
||||
|
transform: translate3d(0, 0, 0); |
||||
|
border: 0; |
||||
|
outline: 0; |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.drawer-hamburger:hover { |
||||
|
cursor: pointer; |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.drawer-hamburger-icon { |
||||
|
position: relative; |
||||
|
display: block; |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
|
||||
|
.drawer-hamburger-icon, |
||||
|
.drawer-hamburger-icon:before, |
||||
|
.drawer-hamburger-icon:after { |
||||
|
width: 100%; |
||||
|
height: 2px; |
||||
|
-webkit-transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); |
||||
|
background-color: #222; |
||||
|
} |
||||
|
|
||||
|
.drawer-hamburger-icon:before, |
||||
|
.drawer-hamburger-icon:after { |
||||
|
position: absolute; |
||||
|
top: -10px; |
||||
|
left: 0; |
||||
|
content: ' '; |
||||
|
} |
||||
|
|
||||
|
.drawer-hamburger-icon:after { |
||||
|
top: 10px; |
||||
|
} |
||||
|
|
||||
|
.drawer-open .drawer-hamburger-icon { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.drawer-open .drawer-hamburger-icon:before, |
||||
|
.drawer-open .drawer-hamburger-icon:after { |
||||
|
top: 0; |
||||
|
} |
||||
|
|
||||
|
.drawer-open .drawer-hamburger-icon:before { |
||||
|
-webkit-transform: rotate(45deg); |
||||
|
-ms-transform: rotate(45deg); |
||||
|
transform: rotate(45deg); |
||||
|
} |
||||
|
|
||||
|
.drawer-open .drawer-hamburger-icon:after { |
||||
|
-webkit-transform: rotate(-45deg); |
||||
|
-ms-transform: rotate(-45deg); |
||||
|
transform: rotate(-45deg); |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
accessibility |
||||
|
\*!------------------------------------*/ |
||||
|
|
||||
|
/*! |
||||
|
* Only display content to screen readers |
||||
|
* See: http://a11yproject.com/posts/how-to-hide-content |
||||
|
*/ |
||||
|
.sr-only { |
||||
|
position: absolute; |
||||
|
overflow: hidden; |
||||
|
clip: rect(0, 0, 0, 0); |
||||
|
width: 1px; |
||||
|
height: 1px; |
||||
|
margin: -1px; |
||||
|
padding: 0; |
||||
|
border: 0; |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Use in conjunction with .sr-only to only display content when it's focused. |
||||
|
* Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 |
||||
|
* Credit: HTML5 Boilerplate |
||||
|
*/ |
||||
|
.sr-only-focusable:active, |
||||
|
.sr-only-focusable:focus { |
||||
|
position: static; |
||||
|
overflow: visible; |
||||
|
clip: auto; |
||||
|
width: auto; |
||||
|
height: auto; |
||||
|
margin: 0; |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Sidebar |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer--sidebar { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
.drawer--sidebar .drawer-contents { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
@media (min-width: 64em) { |
||||
|
.drawer--sidebar .drawer-hamburger { |
||||
|
display: none; |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
|
||||
|
.drawer--sidebar .drawer-nav { |
||||
|
display: block; |
||||
|
-webkit-transform: none; |
||||
|
-ms-transform: none; |
||||
|
transform: none; |
||||
|
position: fixed; |
||||
|
width: 12.5rem; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
/*! Left */ |
||||
|
.drawer--sidebar.drawer--left .drawer-nav { |
||||
|
left: 0; |
||||
|
border-right: 1px solid #ddd; |
||||
|
} |
||||
|
|
||||
|
.drawer--sidebar.drawer--left .drawer-contents { |
||||
|
margin-left: 12.5rem; |
||||
|
} |
||||
|
|
||||
|
/*! Right */ |
||||
|
.drawer--sidebar.drawer--right .drawer-nav { |
||||
|
right: 0; |
||||
|
border-left: 1px solid #ddd; |
||||
|
} |
||||
|
|
||||
|
.drawer--sidebar.drawer--right .drawer-contents { |
||||
|
margin-right: 12.5rem; |
||||
|
} |
||||
|
|
||||
|
/*! container */ |
||||
|
.drawer--sidebar .drawer-container { |
||||
|
max-width: 48rem; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (min-width: 75em) { |
||||
|
.drawer--sidebar .drawer-nav { |
||||
|
width: 16.25rem; |
||||
|
} |
||||
|
|
||||
|
.drawer--sidebar.drawer--left .drawer-contents { |
||||
|
margin-left: 16.25rem; |
||||
|
} |
||||
|
|
||||
|
.drawer--sidebar.drawer--right .drawer-contents { |
||||
|
margin-right: 16.25rem; |
||||
|
} |
||||
|
|
||||
|
/*! container */ |
||||
|
.drawer--sidebar .drawer-container { |
||||
|
max-width: 60rem; |
||||
|
} |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Navbar |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer--navbarTopGutter { |
||||
|
padding-top: 3.75rem; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-navbar-header { |
||||
|
border-bottom: 1px solid #ddd; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar { |
||||
|
z-index: 102; |
||||
|
top: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
/*! .drawer-navbar modifier */ |
||||
|
.drawer-navbar--fixed { |
||||
|
position: fixed; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar-header { |
||||
|
position: relative; |
||||
|
z-index: 102; |
||||
|
box-sizing: border-box; |
||||
|
width: 100%; |
||||
|
height: 3.75rem; |
||||
|
padding: 0 .75rem; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-brand { |
||||
|
line-height: 3.75rem; |
||||
|
display: inline-block; |
||||
|
padding-top: 0; |
||||
|
padding-bottom: 0; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-brand:hover { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-nav { |
||||
|
padding-top: 3.75rem; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-menu { |
||||
|
padding-bottom: 7.5rem; |
||||
|
} |
||||
|
|
||||
|
@media (min-width: 64em) { |
||||
|
.drawer-navbar { |
||||
|
height: 3.75rem; |
||||
|
border-bottom: 1px solid #ddd; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-navbar-header { |
||||
|
position: relative; |
||||
|
display: block; |
||||
|
float: left; |
||||
|
width: auto; |
||||
|
padding: 0; |
||||
|
border: 0; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-menu--right { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-menu li { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-menu-item { |
||||
|
line-height: 3.75rem; |
||||
|
padding-top: 0; |
||||
|
padding-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-hamburger { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-nav { |
||||
|
position: relative; |
||||
|
left: 0; |
||||
|
overflow: visible; |
||||
|
width: auto; |
||||
|
height: 3.75rem; |
||||
|
padding-top: 0; |
||||
|
-webkit-transform: translate3d(0, 0, 0); |
||||
|
transform: translate3d(0, 0, 0); |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-menu { |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
/*! dropdown */ |
||||
|
.drawer-navbar .drawer-dropdown-menu { |
||||
|
position: absolute; |
||||
|
width: 16.25rem; |
||||
|
border: 1px solid #ddd; |
||||
|
} |
||||
|
|
||||
|
.drawer-navbar .drawer-dropdown-menu-item { |
||||
|
padding-left: .75rem; |
||||
|
} |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Dropdown |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer-dropdown-menu { |
||||
|
display: none; |
||||
|
box-sizing: border-box; |
||||
|
width: 100%; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
.drawer-dropdown-menu > li { |
||||
|
width: 100%; |
||||
|
list-style: none; |
||||
|
} |
||||
|
|
||||
|
.drawer-dropdown-menu-item { |
||||
|
line-height: 3.75rem; |
||||
|
display: block; |
||||
|
padding: 0; |
||||
|
padding-right: .75rem; |
||||
|
padding-left: 1.5rem; |
||||
|
text-decoration: none; |
||||
|
color: #222; |
||||
|
} |
||||
|
|
||||
|
.drawer-dropdown-menu-item:hover { |
||||
|
text-decoration: underline; |
||||
|
color: #555; |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
/*! open */ |
||||
|
.drawer-dropdown.open > .drawer-dropdown-menu { |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
/*! drawer-caret */ |
||||
|
.drawer-dropdown .drawer-caret { |
||||
|
display: inline-block; |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
margin-left: 4px; |
||||
|
-webkit-transition: opacity .2s ease, -webkit-transform .2s ease; |
||||
|
transition: opacity .2s ease, -webkit-transform .2s ease; |
||||
|
transition: transform .2s ease, opacity .2s ease; |
||||
|
transition: transform .2s ease, opacity .2s ease, -webkit-transform .2s ease; |
||||
|
-webkit-transform: rotate(0deg); |
||||
|
-ms-transform: rotate(0deg); |
||||
|
transform: rotate(0deg); |
||||
|
vertical-align: middle; |
||||
|
border-top: 4px solid; |
||||
|
border-right: 4px solid transparent; |
||||
|
border-left: 4px solid transparent; |
||||
|
} |
||||
|
|
||||
|
/*! open */ |
||||
|
.drawer-dropdown.open .drawer-caret { |
||||
|
-webkit-transform: rotate(180deg); |
||||
|
-ms-transform: rotate(180deg); |
||||
|
transform: rotate(180deg); |
||||
|
} |
||||
|
/*!------------------------------------*\ |
||||
|
Container |
||||
|
\*!------------------------------------*/ |
||||
|
.drawer-container { |
||||
|
margin-right: auto; |
||||
|
margin-left: auto; |
||||
|
} |
||||
|
|
||||
|
@media (min-width: 64em) { |
||||
|
.drawer-container { |
||||
|
max-width: 60rem; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (min-width: 75em) { |
||||
|
.drawer-container { |
||||
|
max-width: 70rem; |
||||
|
} |
||||
|
} |
@ -0,0 +1,177 @@ |
|||||
|
/*! |
||||
|
* jquery-drawer v3.2.0 |
||||
|
* Flexible drawer menu using jQuery, iScroll and CSS. |
||||
|
* http://git.blivesta.com/drawer
|
||||
|
* License : MIT |
||||
|
* Author : blivesta <design@blivesta.com> (http://blivesta.com/)
|
||||
|
*/ |
||||
|
|
||||
|
;(function umd(factory) { |
||||
|
'use strict'; |
||||
|
if (typeof define === 'function' && define.amd) { |
||||
|
define(['jquery'], factory); |
||||
|
} else if (typeof exports === 'object') { |
||||
|
module.exports = factory(require('jquery')); |
||||
|
} else { |
||||
|
factory(jQuery); |
||||
|
} |
||||
|
}(function Drawer($) { |
||||
|
'use strict'; |
||||
|
var namespace = 'drawer'; |
||||
|
var touches = typeof document.ontouchstart != 'undefined'; |
||||
|
var __ = { |
||||
|
init: function init(options) { |
||||
|
options = $.extend({ |
||||
|
iscroll: { |
||||
|
mouseWheel: true, |
||||
|
preventDefault: false |
||||
|
}, |
||||
|
showOverlay: true |
||||
|
}, options); |
||||
|
|
||||
|
__.settings = { |
||||
|
state: false, |
||||
|
events: { |
||||
|
opened: 'drawer.opened', |
||||
|
closed: 'drawer.closed' |
||||
|
}, |
||||
|
dropdownEvents: { |
||||
|
opened: 'shown.bs.dropdown', |
||||
|
closed: 'hidden.bs.dropdown' |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
__.settings.class = $.extend({ |
||||
|
nav: 'drawer-nav', |
||||
|
toggle: 'drawer-toggle', |
||||
|
overlay: 'drawer-overlay', |
||||
|
open: 'drawer-open', |
||||
|
close: 'drawer-close', |
||||
|
dropdown: 'drawer-dropdown' |
||||
|
}, options.class); |
||||
|
|
||||
|
return this.each(function instantiateDrawer() { |
||||
|
var _this = this; |
||||
|
var $this = $(this); |
||||
|
var data = $this.data(namespace); |
||||
|
|
||||
|
if (!data) { |
||||
|
options = $.extend({}, options); |
||||
|
$this.data(namespace, { options: options }); |
||||
|
|
||||
|
__.refresh.call(_this); |
||||
|
|
||||
|
if (options.showOverlay) { |
||||
|
__.addOverlay.call(_this); |
||||
|
} |
||||
|
|
||||
|
$('.' + __.settings.class.toggle).on('click.' + namespace, function toggle() { |
||||
|
__.toggle.call(_this); |
||||
|
return _this.iScroll.refresh(); |
||||
|
}); |
||||
|
|
||||
|
$(window).resize(function close() { |
||||
|
__.close.call(_this); |
||||
|
return _this.iScroll.refresh(); |
||||
|
}); |
||||
|
|
||||
|
$('.' + __.settings.class.dropdown) |
||||
|
.on(__.settings.dropdownEvents.opened + ' ' + __.settings.dropdownEvents.closed, function onOpenedOrClosed() { |
||||
|
return _this.iScroll.refresh(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
}); // end each
|
||||
|
}, |
||||
|
|
||||
|
refresh: function refresh() { |
||||
|
this.iScroll = new IScroll( |
||||
|
'.' + __.settings.class.nav, |
||||
|
$(this).data(namespace).options.iscroll |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
addOverlay: function addOverlay() { |
||||
|
var _this = this; |
||||
|
var $this = $(this); |
||||
|
var $overlay = $('<div>').addClass(__.settings.class.overlay + ' ' + __.settings.class.toggle); |
||||
|
|
||||
|
return $this.append($overlay); |
||||
|
}, |
||||
|
|
||||
|
toggle: function toggle() { |
||||
|
var _this = this; |
||||
|
|
||||
|
if (__.settings.state) { |
||||
|
return __.close.call(_this); |
||||
|
} else { |
||||
|
return __.open.call(_this); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
open: function open() { |
||||
|
var $this = $(this); |
||||
|
|
||||
|
if (touches) { |
||||
|
$this.on('touchmove.' + namespace, function disableTouch(event) { |
||||
|
event.preventDefault(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return $this |
||||
|
.removeClass(__.settings.class.close) |
||||
|
.addClass(__.settings.class.open) |
||||
|
.css({ 'overflow': 'hidden' }) |
||||
|
.drawerCallback(function triggerOpenedListeners() { |
||||
|
__.settings.state = true; |
||||
|
$this.trigger(__.settings.events.opened); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
close: function close() { |
||||
|
var $this = $(this); |
||||
|
|
||||
|
if (touches) $this.off('touchmove.' + namespace); |
||||
|
|
||||
|
return $this |
||||
|
.removeClass(__.settings.class.open) |
||||
|
.addClass(__.settings.class.close) |
||||
|
.css({ 'overflow': 'auto' }) |
||||
|
.drawerCallback(function triggerClosedListeners() { |
||||
|
__.settings.state = false; |
||||
|
$this.trigger(__.settings.events.closed); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
destroy: function destroy() { |
||||
|
return this.each(function destroyEach() { |
||||
|
var $this = $(this); |
||||
|
$(window).off('.' + namespace); |
||||
|
$this.removeData(namespace); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
$.fn.drawerCallback = function drawerCallback(callback) { |
||||
|
var end = 'transitionend webkitTransitionEnd'; |
||||
|
return this.each(function setAnimationEndHandler() { |
||||
|
var $this = $(this); |
||||
|
$this.on(end, function invokeCallbackOnAnimationEnd() { |
||||
|
$this.off(end); |
||||
|
return callback.call(this); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
$.fn.drawer = function drawer(method) { |
||||
|
if (__[method]) { |
||||
|
return __[method].apply(this, Array.prototype.slice.call(arguments, 1)); |
||||
|
} else if (typeof method === 'object' || !method) { |
||||
|
return __.init.apply(this, arguments); |
||||
|
} else { |
||||
|
$.error('Method ' + method + ' does not exist on jQuery.' + namespace); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
})); |
2148
web_app_drawer/static/lib/js/iscroll-probe.5.2.0.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,292 @@ |
|||||
|
/* Copyright 2016 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
|
||||
|
odoo.define('web_app_drawer', function(require) { |
||||
|
'use strict'; |
||||
|
|
||||
|
var $ = require('$'); |
||||
|
var Menu = require('web.Menu'); |
||||
|
var Class = require('web.Class'); |
||||
|
var SearchView = require('web.SearchView'); |
||||
|
var core = require('web.core'); |
||||
|
|
||||
|
Menu.include({ |
||||
|
|
||||
|
// Force all_outside to prevent app icons from going into more menu
|
||||
|
reflow: function() { |
||||
|
this._super('all_outside'); |
||||
|
}, |
||||
|
|
||||
|
/* Overload to collapse unwanted visible submenus |
||||
|
* @param allow_open bool Switch to allow submenus to be opened |
||||
|
*/ |
||||
|
open_menu: function(id, allowOpen) { |
||||
|
this._super(id); |
||||
|
if (allowOpen) return; |
||||
|
var $clicked_menu = this.$secondary_menus.find('a[data-menu=' + id + ']'); |
||||
|
$clicked_menu.parents('.oe_secondary_submenu').css('display', ''); |
||||
|
}, |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
SearchView.include({ |
||||
|
|
||||
|
// Prevent focus of search field on mobile devices
|
||||
|
toggle_visibility: function (is_visible) { |
||||
|
$('div.oe_searchview_input').last() |
||||
|
.one('focus', $.proxy(this.preventMobileFocus, this)); |
||||
|
return this._super(is_visible); |
||||
|
}, |
||||
|
|
||||
|
// It prevents focusing of search el on mobile
|
||||
|
preventMobileFocus: function(event) { |
||||
|
if (this.isMobile()) { |
||||
|
event.preventDefault(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// For lack of Modernizr, TouchEvent will do
|
||||
|
isMobile: function () { |
||||
|
try{ |
||||
|
document.createEvent('TouchEvent'); |
||||
|
return true; |
||||
|
} catch (ex) { |
||||
|
return false; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
var AppDrawer = Class.extend({ |
||||
|
|
||||
|
LEFT: 'left', |
||||
|
RIGHT: 'right', |
||||
|
UP: 'up', |
||||
|
DOWN: 'down', |
||||
|
|
||||
|
isOpen: false, |
||||
|
keyBuffer: '', |
||||
|
keyBufferTime: 500, |
||||
|
keyBufferTimeoutEvent: false, |
||||
|
dropdownHeightFactor: 0.90, |
||||
|
initialized: false, |
||||
|
|
||||
|
init: function() { |
||||
|
this.directionCodes = { |
||||
|
'left': this.LEFT, |
||||
|
'right': this.RIGHT, |
||||
|
'up': this.UP, |
||||
|
'pageup': this.UP, |
||||
|
'down': this.DOWN, |
||||
|
'pagedown': this.DOWN, |
||||
|
'+': this.RIGHT, |
||||
|
'-': this.LEFT, |
||||
|
}; |
||||
|
this.initDrawer(); |
||||
|
var $clickZones = $('.openerp_webclient_container, ' + |
||||
|
'a.oe_menu_leaf, ' + |
||||
|
'a.oe_menu_toggler' |
||||
|
); |
||||
|
$clickZones.click($.proxy(this.handleClickZones, this)); |
||||
|
core.bus.on('resize', this, this.handleWindowResize); |
||||
|
core.bus.on('keydown', this, this.handleNavKeys); |
||||
|
}, |
||||
|
|
||||
|
// It provides initialization handlers for Drawer
|
||||
|
initDrawer: function() { |
||||
|
this.$el = $('.drawer'); |
||||
|
this.$el.drawer(); |
||||
|
this.$el.one('drawer.opened', $.proxy(this.onDrawerOpen, this)); |
||||
|
this.$el.on('drawer.opened', function setIScrollProbes(){ |
||||
|
var onIScroll = function() { |
||||
|
var transform = (this.iScroll.y) ? this.iScroll.y * -1 : 0; |
||||
|
$(this).find('#appDrawerAppPanelHead').css( |
||||
|
'transform', 'matrix(1, 0, 0, 1, 0, ' + transform + ')' |
||||
|
); |
||||
|
}; |
||||
|
this.iScroll.options.probeType = 2; |
||||
|
this.iScroll.on('scroll', $.proxy(onIScroll, this)); |
||||
|
}); |
||||
|
this.initialized = true; |
||||
|
}, |
||||
|
|
||||
|
// It provides handlers to hide drawer when "unfocused"
|
||||
|
handleClickZones: function() { |
||||
|
this.$el.drawer('close'); |
||||
|
$('.oe_secondary_menus_container') |
||||
|
.parent() |
||||
|
.collapse('hide'); |
||||
|
}, |
||||
|
|
||||
|
// It resizes bootstrap dropdowns for screen
|
||||
|
handleWindowResize: function() { |
||||
|
$('.dropdown-scrollable').css( |
||||
|
'max-height', $(window).height() * this.dropdownHeightFactor |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
// It provides keyboard shortcuts for app drawer nav
|
||||
|
handleNavKeys: function(e) { |
||||
|
if (!this.isOpen){ |
||||
|
return; |
||||
|
} |
||||
|
var directionCode = $.hotkeys.specialKeys[e.keyCode.toString()]; |
||||
|
if (Object.keys(this.directionCodes).indexOf(directionCode) !== -1) { |
||||
|
var $link = this.findAdjacentAppLink( |
||||
|
this.$el.find('a:first, a:focus').last(), |
||||
|
this.directionCodes[directionCode] |
||||
|
); |
||||
|
this.selectAppLink($link); |
||||
|
} else if ($.hotkeys.specialKeys[e.keyCode.toString()] == 'esc') { |
||||
|
this.handleClickZones(); |
||||
|
} else { |
||||
|
var buffer = this.handleKeyBuffer(e.keyCode); |
||||
|
this.selectAppLink(this.searchAppLinks(buffer)); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/* It adds to keybuffer, sets expire timer, and returns buffer |
||||
|
* @returns str of current buffer |
||||
|
*/ |
||||
|
handleKeyBuffer: function(keyCode) { |
||||
|
this.keyBuffer += String.fromCharCode(keyCode); |
||||
|
if (this.keyBufferTimeoutEvent) { |
||||
|
clearTimeout(this.keyBufferTimeoutEvent); |
||||
|
} |
||||
|
this.keyBufferTimeoutEvent = setTimeout( |
||||
|
$.proxy(this.clearKeyBuffer, this), |
||||
|
this.keyBufferTime |
||||
|
); |
||||
|
return this.keyBuffer; |
||||
|
}, |
||||
|
|
||||
|
clearKeyBuffer: function() { |
||||
|
this.keyBuffer = ''; |
||||
|
}, |
||||
|
|
||||
|
/* It performs close actions |
||||
|
* @fires ``drawer.closed`` to the ``core.bus`` |
||||
|
* @listens ``drawer.opened`` and sends to onDrawerOpen |
||||
|
*/ |
||||
|
onDrawerClose: function() { |
||||
|
core.bus.trigger('drawer.closed'); |
||||
|
this.$el.one('drawer.opened', $.proxy(this.onDrawerOpen, this)); |
||||
|
this.isOpen = false; |
||||
|
}, |
||||
|
|
||||
|
/* It finds app links and register event handlers |
||||
|
* @fires ``drawer.opened`` to the ``core.bus`` |
||||
|
* @listens ``drawer.closed`` and sends to :meth:``onDrawerClose`` |
||||
|
*/ |
||||
|
onDrawerOpen: function() { |
||||
|
this.$appLinks = $('.app-drawer-icon-app').parent(); |
||||
|
this.selectAppLink($(this.$appLinks[0])); |
||||
|
this.$el.one('drawer.closed', $.proxy(this.onDrawerClose, this)); |
||||
|
core.bus.trigger('drawer.opened'); |
||||
|
this.isOpen = true; |
||||
|
}, |
||||
|
|
||||
|
// It selects an app link visibly
|
||||
|
selectAppLink: function($appLink) { |
||||
|
if ($appLink) { |
||||
|
$appLink.focus(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/* It returns first App Link by its name according to query |
||||
|
* @param query str to search |
||||
|
* @return jQuery obj |
||||
|
*/ |
||||
|
searchAppLinks: function(query) { |
||||
|
return this.$appLinks.filter(function() { |
||||
|
return $(this).data('menuName').toUpperCase().startsWith(query); |
||||
|
}).first(); |
||||
|
}, |
||||
|
|
||||
|
/* It returns the link adjacent to $appLink in provided direction. |
||||
|
* It also handles edge cases in the following ways: |
||||
|
* * Moves to last link if LEFT on first |
||||
|
* * Moves to first link if PREV on last |
||||
|
* * Moves to first link of following row if RIGHT on last in row |
||||
|
* * Moves to last link of previous row if LEFT on first in row |
||||
|
* * Moves to top link in same column if DOWN on bottom row |
||||
|
* * Moves to bottom link in same column if UP on top row |
||||
|
* @param $appLink jQuery obj of App icon link |
||||
|
* @param direction str of direction to go (constants LEFT, UP, etc.) |
||||
|
* @return jQuery obj for adjacent applink |
||||
|
*/ |
||||
|
findAdjacentAppLink: function($appLink, direction) { |
||||
|
|
||||
|
var obj = [], |
||||
|
$objs = this.$appLinks; |
||||
|
|
||||
|
switch(direction){ |
||||
|
case this.LEFT: |
||||
|
obj = $objs[$objs.index($appLink) - 1]; |
||||
|
if (!obj) { |
||||
|
obj = $objs[$objs.length - 1]; |
||||
|
} |
||||
|
break; |
||||
|
case this.RIGHT: |
||||
|
obj = $objs[$objs.index($appLink) + 1]; |
||||
|
if (!obj) { |
||||
|
obj = $objs[0]; |
||||
|
} |
||||
|
break; |
||||
|
case this.UP: |
||||
|
$objs = this.getRowObjs($appLink, this.$appLinks); |
||||
|
obj = $objs[$objs.index($appLink) - 1]; |
||||
|
if (!obj) { |
||||
|
obj = $objs[$objs.length - 1]; |
||||
|
} |
||||
|
break; |
||||
|
case this.DOWN: |
||||
|
$objs = this.getRowObjs($appLink, this.$appLinks); |
||||
|
obj = $objs[$objs.index($appLink) + 1]; |
||||
|
if (!obj) { |
||||
|
obj = $objs[0]; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (obj.length) { |
||||
|
event.preventDefault(); |
||||
|
} |
||||
|
|
||||
|
return $(obj); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/* It returns els in the same row |
||||
|
* @param @obj jQuery object to get row for |
||||
|
* @param $grid jQuery objects representing grid |
||||
|
* @return $objs jQuery objects of row |
||||
|
*/ |
||||
|
getRowObjs: function($obj, $grid) { |
||||
|
// Filter by object which middle lies within left/right bounds
|
||||
|
function filterWithin(left, right) { |
||||
|
return function() { |
||||
|
var $this = $(this), |
||||
|
thisMiddle = $this.offset().left + ($this.width() / 2); |
||||
|
return thisMiddle >= left && thisMiddle <= right; |
||||
|
}; |
||||
|
} |
||||
|
var left = $obj.offset().left, |
||||
|
right = left + $obj.outerWidth(); |
||||
|
return $grid.filter(filterWithin(left, right)); |
||||
|
}, |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
// It inits a new AppDrawer when the web client is ready
|
||||
|
core.bus.on('web_client_ready', null, function () { |
||||
|
new AppDrawer(); |
||||
|
}); |
||||
|
|
||||
|
return { |
||||
|
'AppDrawer': AppDrawer, |
||||
|
'SearchView': SearchView, |
||||
|
'Menu': Menu, |
||||
|
}; |
||||
|
|
||||
|
}); |
@ -0,0 +1,85 @@ |
|||||
|
/* Copyright 2016 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ |
||||
|
|
||||
|
.app-drawer-nav { |
||||
|
border-color: @dropdown-border; |
||||
|
background-color: @dropdown-bg; |
||||
|
border: 1px solid @dropdown-fallback-border; // IE8 fallback |
||||
|
border: 1px solid @dropdown-border; |
||||
|
-webkit-border-radius: @border-radius-base; |
||||
|
-moz-border-radius: @border-radius-base; |
||||
|
border-radius: @border-radius-base; |
||||
|
.box-shadow(0 6px 12px rgba(0, 0, 0, .175)); |
||||
|
background-clip: padding-box; |
||||
|
|
||||
|
.navbar-left { |
||||
|
|
||||
|
width: 100%; |
||||
|
|
||||
|
li { |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.app-drawer-title { |
||||
|
float: none; |
||||
|
} |
||||
|
|
||||
|
.app-drawer-panel-title { |
||||
|
margin-top: 4px; |
||||
|
} |
||||
|
|
||||
|
.app-drawer-icon-app { |
||||
|
height: @app-drawer-icon-size; |
||||
|
width: @app-drawer-icon-size; |
||||
|
margin: @app-drawer-icon-margin; |
||||
|
} |
||||
|
|
||||
|
.panel-body { |
||||
|
padding-top: @app-drawer-title-height; |
||||
|
} |
||||
|
|
||||
|
#appDrawerAppPanelHead { |
||||
|
position: absolute; |
||||
|
height: @app-drawer-title-height; |
||||
|
width: 100%; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.drawer-nav { |
||||
|
width: @app-drawer-width; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
.drawer--left .drawer-nav { |
||||
|
left: -@app-drawer-width; |
||||
|
} |
||||
|
|
||||
|
.drawer--left.drawer-open .drawer-hamburger { |
||||
|
left: @app-drawer-width; |
||||
|
} |
||||
|
|
||||
|
.drawer--right .drawer-nav { |
||||
|
right: -@app-drawer-width; |
||||
|
} |
||||
|
|
||||
|
.drawer-open .oe-right-toolbar { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.drawer-closed .oe-right-toolbar { |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
/* App Drawer Toggle */ |
||||
|
|
||||
|
.app-drawer-toggle { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.app-drawer-toggle.navbar-toggle { |
||||
|
margin-left: 1em; |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
/* Copyright 2016 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ |
||||
|
|
||||
|
body { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
main { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
/* Copyright 2016 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ |
||||
|
|
||||
|
.main-nav { |
||||
|
margin-bottom: 0; |
||||
|
min-height: @app-drawer-navbar-height; |
||||
|
} |
||||
|
|
||||
|
.main-nav ul.nav > li > a { |
||||
|
padding: @app-drawer-navbar-padding-vertical @app-drawer-padding-horizontal; |
||||
|
} |
||||
|
|
||||
|
.oe_topbar_avatar { |
||||
|
margin-top: -@app-drawer-padding-horizontal; |
||||
|
height: @app-drawer-navbar-height; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
a.navbar-collapse.collapse { |
||||
|
@media (min-width: @screen-sm) { |
||||
|
padding-bottom: @app-drawer-navbar-padding-vertical; |
||||
|
padding-top: @app-drawer-navbar-padding-vertical; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dropdown-scrollable { |
||||
|
overflow-x: hidden; |
||||
|
} |
||||
|
|
||||
|
.badge { |
||||
|
position: absolute; |
||||
|
right: @app-drawer-padding-horizontal; |
||||
|
} |
||||
|
|
||||
|
@media (max-width: @screen-sm - 1px) { |
||||
|
#odooMenuBarNav[aria-expanded="false"] { |
||||
|
/* Hack to hide the visibly expanded mobile menu on load. */ |
||||
|
position: absolute; |
||||
|
z-index: -9999; |
||||
|
} |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
/* Copyright 2016 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ |
||||
|
|
||||
|
// App Drawer / Icons |
||||
|
|
||||
|
@app-drawer-icon-size: 7em; |
||||
|
@app-drawer-icon-margin: 1em; |
||||
|
@app-drawer-width: 80%; |
||||
|
@app-drawer-title-height: @navbar-height; |
||||
|
|
||||
|
// Navbar |
||||
|
|
||||
|
@app-drawer-navbar-height: @navbar-height / 2; |
||||
|
@app-drawer-navbar-padding-vertical: @navbar-padding-vertical / 2; |
||||
|
@app-drawer-padding-horizontal: @navbar-padding-horizontal / 2; |
@ -0,0 +1,291 @@ |
|||||
|
/* Copyright 2016 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
|
||||
|
odoo.define_section('web_app_drawer', ['web_app_drawer'], function(test) { |
||||
|
"use strict"; |
||||
|
|
||||
|
// It provides a base drawer compatible interface for testing
|
||||
|
self.initInterface = function(AppDrawer) { |
||||
|
|
||||
|
var $el = $('<div class="drawer drawer--left">'); |
||||
|
$el.append( |
||||
|
$('<header role="banner">') |
||||
|
.append( |
||||
|
$('<button class="drawer-toggle"><span class="drawer-hamburger-icon">') |
||||
|
) |
||||
|
.append( |
||||
|
$('<nav class="drawer-nav"><ul class="drawer-menu"><li class="drawer-menu-item">') |
||||
|
) |
||||
|
.append( |
||||
|
$('<div class="panel-title" id="appDrawerAppPanelHead">') |
||||
|
) |
||||
|
).append($('<main role="main">')); |
||||
|
|
||||
|
self.$clickZone = $('<a class="oe_menu_leaf">'); |
||||
|
|
||||
|
self.$secondaryMenu = $('<div><div class="oe_secondary_menus_container">'); |
||||
|
|
||||
|
self.$dropdown = $('<div class="dropdown-scrollable">'); |
||||
|
|
||||
|
$el.append(self.$clickZone) |
||||
|
.append(self.$secondaryMenu) |
||||
|
.append(self.$dropdown); |
||||
|
|
||||
|
var $document = $("#qunit-fixture"); |
||||
|
$document.append($el); |
||||
|
|
||||
|
self.drawer = new AppDrawer.AppDrawer(); |
||||
|
|
||||
|
return $document; |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
self.linkGrid = function() { |
||||
|
for(var i=0; i < 3; i++){ |
||||
|
self.drawer.$el.append( |
||||
|
$('<div class="row">').append( |
||||
|
$('<a class="col-md-6" id="a_' + i + '"><span class="app-drawer-icon-app /></a>' + |
||||
|
'<a class="col-md-6" id="b_' + i + '"><span class="app-drawer-icon-app /></a>' |
||||
|
) |
||||
|
) |
||||
|
); |
||||
|
self.drawer.$appLinks = $('a.col-md-6'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
test('It should set initialized after success init', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
assert.ok(self.drawer.initialized); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should close drawer after click on clickZone', |
||||
|
{asserts: 1}, |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.$clickZone.click(); |
||||
|
var d = $.Deferred(); |
||||
|
setTimeout(function() { |
||||
|
assert.ok(self.drawer.$el.hasClass('drawer-close')); |
||||
|
d.resolve(); |
||||
|
}, 100); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should collapse open secondary menus during handleClickZones', |
||||
|
{asserts: 1}, |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.$clickZone.click(); |
||||
|
var d = $.Deferred(); |
||||
|
setTimeout(function() { |
||||
|
assert.equal(self.$secondaryMenu.attr('aria-expanded'), 'false'); |
||||
|
d.resolve(); |
||||
|
}, 100); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should update max-height on scrollable dropdowns', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.handleWindowResize(); |
||||
|
var height = $(window).height() * self.drawer.dropdownHeightFactor; |
||||
|
assert.equal( |
||||
|
self.$dropdown.css('max-height'), |
||||
|
height + 'px' |
||||
|
); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should return keybuffer + new key', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.keyBuffer = 'TES'; |
||||
|
var res = self.drawer.handleKeyBuffer(84); |
||||
|
assert.equal(res, 'TEST'); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should clear keybuffer after timeout', |
||||
|
{asserts: 1}, |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.keyBuffer = 'TES'; |
||||
|
self.drawer.keyBufferTime = 10; |
||||
|
self.drawer.handleKeyBuffer(84); |
||||
|
var d = $.Deferred(); |
||||
|
setTimeout(function() { |
||||
|
assert.equal(self.drawer.keyBuffer, ""); |
||||
|
d.resolve(); |
||||
|
}, 100); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should trigger core bus event for drawer close', |
||||
|
['web.core'], {asserts: 1}, |
||||
|
function(assert, AppDrawer, core) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.onDrawerOpen(); |
||||
|
var d = $.Deferred(); |
||||
|
core.bus.on('drawer.closed', this, function() { |
||||
|
assert.ok(true); |
||||
|
d.resolve(); |
||||
|
}); |
||||
|
self.drawer.$el.trigger({type: 'drawer.closed'}); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should set isOpen to false when closing', |
||||
|
{asserts: 1}, |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.onDrawerOpen(); |
||||
|
var d = $.Deferred(); |
||||
|
setTimeout(function() { |
||||
|
assert.equal(self.drawer.isOpen, false); |
||||
|
d.resolve(); |
||||
|
}); |
||||
|
self.drawer.$el.trigger({type: 'drawer.closed'}); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should set isOpen to true when opening', |
||||
|
{asserts: 1}, |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
var d = $.Deferred(); |
||||
|
self.drawer.$el.trigger({type: 'drawer.opened'}); |
||||
|
setTimeout(function() { |
||||
|
assert.ok(self.drawer.isOpen); |
||||
|
d.resolve(); |
||||
|
}); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should trigger core bus event for drawer open', |
||||
|
['web.core'], {asserts: 1}, |
||||
|
function(assert, AppDrawer, core) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.onDrawerOpen(); |
||||
|
var d = $.Deferred(); |
||||
|
core.bus.on('drawer.opened', this, function() { |
||||
|
assert.ok(true); |
||||
|
d.resolve(); |
||||
|
}); |
||||
|
self.drawer.$el.trigger({type: 'drawer.opened'}); |
||||
|
return d; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose link to right', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#a_1'), |
||||
|
$expect = $('#a_2'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.RIGHT |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose link to left', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#a_2'), |
||||
|
$expect = $('#a_1'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.LEFT |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose link above', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#a_1'), |
||||
|
$expect = $('#a_0'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.UP |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose link below', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#a_1'), |
||||
|
$expect = $('#a_2'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.DOWN |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose first link if next on last', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#b_2'), |
||||
|
$expect = $('#a_0'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.RIGHT |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose bottom link if up on top', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#a_0'), |
||||
|
$expect = $('#a_2'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.UP |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should choose top link if down on bottom', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.linkGrid(); |
||||
|
var $appLink = $('#a_2'), |
||||
|
$expect = $('#a_0'), |
||||
|
$res = self.drawer.findAdjacentAppLink( |
||||
|
$appLink, self.drawer.DOWN |
||||
|
); |
||||
|
assert.equal($res[0].id, $expect[0].id); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
test('It should move the panel header to negative iScroll.y', |
||||
|
function(assert, AppDrawer) { |
||||
|
self.initInterface(AppDrawer); |
||||
|
self.drawer.$el.iScroll.y = -200; |
||||
|
self.drawer.onIScroll(); |
||||
|
var $header = $('#appDrawerAppPanelHead'); |
||||
|
assert.equal( |
||||
|
$header.css('transform'), |
||||
|
'matrix(1, 0, 0, 1, 0, 200)' |
||||
|
); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
}); |
@ -0,0 +1,16 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from openerp.tests import HttpCase |
||||
|
|
||||
|
|
||||
|
class TestUi(HttpCase): |
||||
|
|
||||
|
def test_ui_web(self): |
||||
|
"""Test backend tests.""" |
||||
|
self.phantom_js( |
||||
|
"/web/tests?module=web_app_drawer", |
||||
|
"", |
||||
|
login="admin", |
||||
|
) |
@ -0,0 +1,46 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<!-- |
||||
|
Copyright 2016 LasLabs Inc. |
||||
|
@author Dave Lasley <dave@laslabs.com> |
||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
--> |
||||
|
|
||||
|
<odoo> |
||||
|
<template id="assets_backend" name="Open Mobile Assets" inherit_id="web.assets_backend"> |
||||
|
<xpath expr="."> |
||||
|
<link rel="stylesheet" |
||||
|
type="text/css" |
||||
|
href="/web_app_drawer/static/lib/css/drawer.3.2.0.css" |
||||
|
/> |
||||
|
<link rel="stylesheet" |
||||
|
href="/web_app_drawer/static/src/less/main.less" |
||||
|
/> |
||||
|
<link rel="stylesheet" |
||||
|
href="/web_app_drawer/static/src/less/navbar.less" |
||||
|
/> |
||||
|
<link rel="stylesheet" |
||||
|
href="/web_app_drawer/static/src/less/app_drawer.less" |
||||
|
/> |
||||
|
<link rel="stylesheet" |
||||
|
href="/web_app_drawer/static/src/less/variables.less" |
||||
|
/> |
||||
|
<script type="application/javascript" |
||||
|
src="/web_app_drawer/static/lib/js/iscroll-probe.5.2.0.js" |
||||
|
/> |
||||
|
<script type="application/javascript" |
||||
|
src="/web_app_drawer/static/lib/js/drawer.3.2.0.js" |
||||
|
/> |
||||
|
<script type="application/javascript" |
||||
|
src="/web_app_drawer/static/src/js/web_app_drawer.js" |
||||
|
/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
<template id="qunit_suite" inherit_id="web.qunit_suite"> |
||||
|
<xpath expr="//html/head" position="inside"> |
||||
|
<script type="application/javascript" |
||||
|
src="/web_app_drawer/static/tests/js/web_app_drawer.js" |
||||
|
/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,236 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<!-- |
||||
|
Copyright 2016 LasLabs Inc. |
||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
--> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<template id="webclient_bootstrap" |
||||
|
inherit_id="web.webclient_bootstrap" |
||||
|
name="App Drawer - Web Client" |
||||
|
> |
||||
|
|
||||
|
<xpath expr="//div[@class='oe_leftbar']" position="replace" /> |
||||
|
|
||||
|
<xpath expr="//t[@t-set='head']" position="inside"> |
||||
|
<meta charset="utf-8" /> |
||||
|
<meta http-equiv="cleartype" content="on" /> |
||||
|
<meta name="MobileOptimized" content="320" /> |
||||
|
<meta name="HandheldFriendly" content="True" /> |
||||
|
<meta name="apple-mobile-web-app-capable" content="yes" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" /> |
||||
|
</xpath> |
||||
|
|
||||
|
<xpath expr="//nav[@id='oe_main_menu_navbar']" position="replace"> |
||||
|
|
||||
|
<t t-set="body_classname" t-value="'drawer drawer--left'" /> |
||||
|
|
||||
|
<header role="banner"> |
||||
|
<nav id="odooAppDrawer" class="app-drawer-nav drawer-nav" role="navigation"> |
||||
|
<t t-call="web.menu" /> |
||||
|
</nav> |
||||
|
|
||||
|
<nav class="navbar navbar-default main-nav" |
||||
|
role="navigation" |
||||
|
groups="base.group_user,base.group_portal" |
||||
|
> |
||||
|
<div class="container-fluid"> |
||||
|
|
||||
|
<div class="navbar-header"> |
||||
|
|
||||
|
<a href="#" |
||||
|
class="drawer-toggle pull-left navbar-collapse collapse btn btn-default app-drawer-toggle" |
||||
|
accesskey="A" |
||||
|
> |
||||
|
<span class="sr-only">Toggle App Drawer</span> |
||||
|
<i class="fa fa-th fa-lg app-drawer-icon-open" /> |
||||
|
</a> |
||||
|
|
||||
|
<button type="button" |
||||
|
class="app-drawer-toggle drawer-toggle pull-left navbar-toggle collapsed" |
||||
|
> |
||||
|
<span class="sr-only">Toggle App Drawer</span> |
||||
|
<i class="fa fa-th fa-lg app-drawer-icon-open" /> |
||||
|
</button> |
||||
|
|
||||
|
<button type="button" |
||||
|
id="odooMenuBarToggle" |
||||
|
class="navbar-toggle collapsed pull-right" |
||||
|
data-toggle="collapse" |
||||
|
data-target="#odooMenuBarNav" |
||||
|
> |
||||
|
<span class="sr-only">Toggle Navigation</span> |
||||
|
<i class="fa fa-bars fa-lg" /> |
||||
|
</button> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div class="collapse navbar-collapse text-center" |
||||
|
id="odooMenuBarNav" |
||||
|
data-parent="#odooMenuBarToggle" |
||||
|
aria-expanded="false" |
||||
|
> |
||||
|
<div class="oe_leftbar" |
||||
|
groups="base.group_user,base.group_portal" |
||||
|
> |
||||
|
<t t-call="web.menu_secondary" /> |
||||
|
</div> |
||||
|
<div class="nav navbar-nav navbar-right"> |
||||
|
<ul class="nav navbar-nav navbar-right oe_user_menu_placeholder" |
||||
|
style="display: none;" |
||||
|
/> |
||||
|
<ul class="nav navbar-nav navbar-right oe_systray" |
||||
|
style="display: none;" |
||||
|
/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</nav> |
||||
|
|
||||
|
</header> |
||||
|
|
||||
|
</xpath> |
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
<template id="menu_secondary" |
||||
|
inherit_id="web.menu_secondary" |
||||
|
name="App Drawer - Secondary Menu" |
||||
|
> |
||||
|
|
||||
|
<xpath expr="//a[@class='oe_logo']" position="replace" /> |
||||
|
|
||||
|
<xpath expr="//div[@class='oe_footer']" position="replace" /> |
||||
|
|
||||
|
<xpath expr="//div[@class='oe_secondary_menus_container']/t" position="replace"> |
||||
|
|
||||
|
<t t-foreach="menu_data['children']" t-as="menu"> |
||||
|
<ul style="display: none" class="oe_secondary_menu nav navbar-nav" t-att-data-menu-parent="menu['id']"> |
||||
|
<t t-call="web.menu_secondary_submenu" /> |
||||
|
</ul> |
||||
|
</t> |
||||
|
|
||||
|
</xpath> |
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
<template id="menu_secondary_submenu" |
||||
|
inherit_id="web.menu_secondary_submenu" |
||||
|
name="App Drawer - Secondary Submenu" |
||||
|
> |
||||
|
|
||||
|
<xpath expr="//ul" position="replace"> |
||||
|
|
||||
|
<t t-foreach="menu['children']" t-as="menu"> |
||||
|
<t t-if="menu['children']"> |
||||
|
<li t-attf-class="{{ 'dropdown-header' if submenu else '' }}"> |
||||
|
<t t-if="submenu"> |
||||
|
<t t-esc="menu['name']" /> |
||||
|
<t t-call="web.menu_secondary_submenu"> |
||||
|
<t t-set="submenu" t-value="True" /> |
||||
|
</t> |
||||
|
</t> |
||||
|
<t t-if="not submenu"> |
||||
|
<a href="#" |
||||
|
class="dropdown-toggle" |
||||
|
data-toggle="dropdown" |
||||
|
role="button" |
||||
|
aria-haspopup="true" |
||||
|
aria-expanded="false" |
||||
|
> |
||||
|
<t t-esc="menu['name']" /> |
||||
|
<span class="caret" /> |
||||
|
</a> |
||||
|
<ul t-if="menu['children']" |
||||
|
t-attf-class="dropdown-menu oe_secondary_submenu dropdown-scrollable" |
||||
|
> |
||||
|
<t t-call="web.menu_secondary_submenu"> |
||||
|
<t t-set="submenu" t-value="True" /> |
||||
|
</t> |
||||
|
</ul> |
||||
|
</t> |
||||
|
</li> |
||||
|
</t> |
||||
|
<t t-if="not menu['children']"> |
||||
|
<li> |
||||
|
<t t-call="web.menu_link" /> |
||||
|
</li> |
||||
|
</t> |
||||
|
</t> |
||||
|
|
||||
|
</xpath> |
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
<template id="menu_link" |
||||
|
inherit_id="web.menu_link" |
||||
|
name="App Drawer - Menu Link" |
||||
|
> |
||||
|
|
||||
|
<xpath expr="//a" position="attributes"> |
||||
|
<attribute name="t-att-data-menu-name">menu['name']</attribute> |
||||
|
</xpath> |
||||
|
|
||||
|
<xpath expr="//span[@class='oe_menu_text']" position="replace"> |
||||
|
<t t-if="display_images"> |
||||
|
<img t-attf-src="/web/image/ir.ui.menu/{{ menu['id'] }}/web_icon_data" |
||||
|
class="app-drawer-icon-app img-rounded" |
||||
|
t-att-alt="menu['name']" |
||||
|
t-att-title="menu['name']" |
||||
|
/> |
||||
|
<p class="app-drawer-title text-center"> |
||||
|
<t t-esc="menu['name']" /> |
||||
|
</p> |
||||
|
</t> |
||||
|
<t t-if="not display_images"> |
||||
|
<span class="oe_menu_text"> |
||||
|
<t t-esc="menu['name']" /> |
||||
|
</span> |
||||
|
</t> |
||||
|
</xpath> |
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
<template id="menu" |
||||
|
inherit_id="web.menu" |
||||
|
name="App Drawer - Menu" |
||||
|
> |
||||
|
|
||||
|
<xpath expr="//ul[contains(@class, 'oe_systray')]" position="replace" /> |
||||
|
|
||||
|
<xpath expr="//ul[contains(@class, 'oe_user_menu_placeholder')]" position="replace" /> |
||||
|
|
||||
|
<xpath expr="//ul[contains(@class, 'oe_application_menu_placeholder')]" position="replace"> |
||||
|
|
||||
|
<div class="panel-default app-drawer-app-panel" id="appDrawerAppMenu"> |
||||
|
<div class="panel-heading" id="appDrawerAppPanelHead"> |
||||
|
<h4 class="app-drawer-panel-title"> |
||||
|
<a href="#" class="app-drawer-icon-close drawer-toggle"> |
||||
|
<i class="fa fa-lg fa-chevron-left" /> |
||||
|
Apps |
||||
|
</a> |
||||
|
</h4> |
||||
|
</div> |
||||
|
<div class="panel-body" id="appDrawerAppPanelBody"> |
||||
|
<ul class="nav navbar-nav navbar-left oe_application_menu_placeholder" style="display: none;"> |
||||
|
<li t-foreach="menu_data['children']" t-as="menu" class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center"> |
||||
|
<t t-call="web.menu_link"> |
||||
|
<t t-set="display_images" t-value="1" /> |
||||
|
</t> |
||||
|
</li> |
||||
|
<li id="menu_more_container" class="dropdown" style="display: none;"> |
||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a> |
||||
|
<ul id="menu_more" class="dropdown-menu"></ul> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</xpath> |
||||
|
|
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue