[MERGE] backend front end integration

Render the top-menu (backend menu or frontend editbar) server-side.
Use Fontawesome icons (mail, timezone, chat).
Refactor website widgets, split Website.editorBarin editorbar, ace, editorbar- content, editorbarCustomize.
This commit is contained in:
Simon Lejeune 2014-06-27 11:03:34 +02:00 committed by Antony Lesuisse
parent 864e94a1dc
commit 195fa93168
76 changed files with 903 additions and 940 deletions

View File

@ -0,0 +1,3 @@
.oe_systray #oe_attendance_sign_in_out_icon {
font-color: white;
}

View File

@ -1,33 +0,0 @@
.openerp .oe_attendance_status {
height: 32px;
width: 32px;
display: inline-block;
}
.openerp .oe_attendance_signin {
float: right;
height: 32px;
width: 32px;
background: url(/hr_attendance/static/src/img/emp-out32.png);
cursor: pointer;
}
.openerp .oe_attendance_signin:hover {
background: url(/hr_attendance/static/src/img/emp-out-disable32.png);
}
.openerp .oe_attendance_status.oe_attendance_signed .oe_attendance_signin {
display: none;
}
.openerp .oe_attendance_signout {
float:right;
height: 32px;
width: 32px;
background: url(/hr_attendance/static/src/img/emp-in32.png);
cursor: pointer;
}
.openerp .oe_attendance_signout:hover {
background: url(/hr_attendance/static/src/img/emp-in-disable32.png);
}
.openerp .oe_attendance_status.oe_attendance_nosigned .oe_attendance_signout {
display: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -14,15 +14,14 @@ openerp.hr_attendance = function (instance) {
start: function() {
var self = this;
var tmp = function() {
this.$el.toggleClass("oe_attendance_nosigned", ! this.get("signed_in"));
this.$el.toggleClass("oe_attendance_signed", this.get("signed_in"));
var $sign_in_out_icon = this.$('#oe_attendance_sign_in_out_icon');
$sign_in_out_icon.toggleClass("fa-sign-in", ! this.get("signed_in"));
$sign_in_out_icon.toggleClass("fa-sign-out", this.get("signed_in"));
};
this.on("change:signed_in", this, tmp);
_.bind(tmp, this)();
this.$(".oe_attendance_signin").click(function() {
self.do_update_attendance();
});
this.$(".oe_attendance_signout").click(function() {
this.$(".oe_attendance_sign_in_out").click(function(ev) {
ev.preventDefault();
self.do_update_attendance();
});
this.$el.tooltip({

View File

@ -1,13 +1,9 @@
<template>
<t t-name="AttendanceSlider">
<div class="oe_attendance_status oe_attendance_nosigned" data-toggle="tooltip">
<div class="oe_attendance_signin"></div>
<div class="oe_attendance_signout"></div>
</div>
<li class="oe_attendance_status" data-toggle="tooltip">
<a href="#" class="oe_attendance_sign_in_out">
<i id="oe_attendance_sign_in_out_icon" class="fa fa-sign-in"/>
</a>
</li>
</t>
</template>

View File

@ -5,8 +5,8 @@
<data>
<template id="assets_backend" name="hr_attendance assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/hr_attendance/static/src/css/slider.css"/>
<script type="text/javascript" src="/hr_attendance/static/src/js/attendance.js"></script>
<link rel="stylesheet" href="/hr_attendance/static/src/css/attendance.css"/>
</xpath>
</template>
</data>

View File

@ -11,11 +11,9 @@
visibility: hidden;
}
}
/* button */
.openerp .oe_topbar_imbutton {
cursor: pointer;
.oe_systray #oe_topbar_im_icon {
color: white;
}
.openerp .oe_topbar_item.oe_topbar_imbutton .oe_e {
position: relative;

View File

@ -25,7 +25,7 @@
im.appendTo(instance.client.$el);
var button = new instance.im.ImTopButton(this);
button.on("clicked", im, im.switch_display);
button.appendTo(instance.webclient.$el.find('.oe_systray'));
button.appendTo(window.$('.oe_systray'));
});
return this._super.apply(this, arguments);
},
@ -36,7 +36,8 @@
events: {
"click": "clicked",
},
clicked: function() {
clicked: function(ev) {
ev.preventDefault();
this.trigger("clicked");
},
});
@ -80,7 +81,7 @@
});
},
calc_box: function() {
var $topbar = instance.client.$(".navbar"); // .oe_topbar is replaced with .navbar of bootstrap3
var $topbar = window.$('#oe_main_menu_navbar'); // .oe_topbar is replaced with .navbar of bootstrap3
var top = $topbar.offset().top + $topbar.height();
top = Math.max(top - $(window).scrollTop(), 0);
this.$el.css("top", top);

View File

@ -14,9 +14,11 @@
</div>
</t>
<t t-name="ImTopButton">
<div t-att-title='_t("Display Instant Messaging")' class="oe_topbar_item oe_topbar_imbutton">
<span class="oe_e">+</span>
</div>
<li t-att-title='_t("Display Instant Messaging")'>
<a href="#">
<i id="oe_topbar_imbutton_icon" class="fa fa-comments-o"/>
</a>
</li>
</t>
<t t-name="UserWidget">
<div class="oe_im_user">

View File

@ -1,29 +1,7 @@
/* ------------ TOPBAR MAIL BUTTON --------------- */
/* FIXME this css is not very pretty because it uses a
* 'button' element wich comes with a lot of inappropriate
* styling. Entypo is also a headache to center properly
* */
.openerp .oe_topbar_item.oe_topbar_compose_full_email{
padding: 0px;
width: 32px;
height: 32px;
}
.openerp .oe_topbar_item.oe_topbar_compose_full_email button{
position: relative;
top: -3px; /* centering entypo ... urgh */
box-sizing: border-box;
border: none;
box-shadow: none;
.oe_systray #oe_topbar_compose_full_email_icon {
color: white;
background: none;
text-shadow: 0px 1px 2px black;
width: 32px;
height: 32px;
padding: 0px;
margin: 0px;
border-radius: 0px;
margin-right: 15px;
}
/* ---- GENERIC FOR MAIL-RELATED STUFF ---- */

View File

@ -12,7 +12,9 @@ openerp_announcement = function(instance) {
}
var self = this;
var config_parameter = new instance.web.Model('ir.config_parameter');
var $bar = this.$el.find('.announcement_bar');
$(openerp.qweb.render('WebClient.announcement_bar')).prependTo($('body'));
var $bar = $('#announcement_bar_table');
return config_parameter.call('get_param', ['database.uuid', false]).then(function(dbuuid) {
if (!dbuuid) {
return;

View File

@ -1996,11 +1996,12 @@ openerp.mail = function (session) {
template:'mail.ComposeMessageTopButton',
start: function () {
this.$('button').on('click', this.on_compose_message );
this.$el.on('click', this.on_compose_message);
this._super();
},
on_compose_message: function (event) {
event.preventDefault();
event.stopPropagation();
var action = {
type: 'ir.actions.act_window',
@ -2021,7 +2022,8 @@ openerp.mail = function (session) {
this._super.apply(this, arguments);
this.update_promise.then(function() {
var mail_button = new session.web.ComposeMessageTopButton();
mail_button.appendTo(session.webclient.$el.find('.oe_systray'));
mail_button.appendTo(session.webclient.$el.parents().find('.oe_systray'));
openerp.web.bus.trigger('resize'); // Re-trigger the reflow logic
});
},
});

View File

@ -1,12 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-extend="WebClient">
<t t-jquery="table.oe_webclient" t-operation="prepend">
<td colspan="2" class="announcement_bar">
<span class="message"></span>
<span class="url"><a href="https://services.openerp.com/openerp-enterprise/ab/register" target="_blank"></a></span>
<span class="close"></span>
</td>
<t t-name="WebClient.announcement_bar">
<div class="openerp" id="announcement_bar_table">
<table class="oe_webclient">
<tr>
<td colspan="2" class="announcement_bar">
<span class="message"></span>
<span class="url">
<a href="https://services.openerp.com/openerp-enterprise/ab/register" target="_blank"></a>
</span>
<span class="close"></span>
</td>
</tr>
</table>
</div>
</t>
</t>
</templates>

View File

@ -340,9 +340,11 @@
render of the button on the user bar for open wizard compose message
-->
<t t-name="mail.ComposeMessageTopButton">
<div title='Compose new Message' class="oe_topbar_item oe_topbar_compose_full_email">
<button class="oe_e">%</button>
</div>
<li title='Compose new Message'>
<a href="#">
<i id="oe_topbar_compose_full_email_icon" class="fa fa-envelope-o"/>
</a>
</li>
</t>
<!-- mail.thread.message.vote

View File

@ -510,7 +510,11 @@ class Home(http.Controller):
if request.session.uid:
if kw.get('redirect'):
return werkzeug.utils.redirect(kw.get('redirect'), 303)
return request.render('web.webclient_bootstrap')
if not request.uid:
request.uid = request.session.uid
menu_data = request.registry['ir.ui.menu'].load_menus(request.cr, request.uid, context=request.context)
return request.render('web.webclient_bootstrap', qcontext={'menu_data': menu_data})
else:
return login_redirect()
@ -892,70 +896,6 @@ class Session(http.Controller):
class Menu(http.Controller):
@http.route('/web/menu/get_user_roots', type='json', auth="user")
def get_user_roots(self):
""" Return all root menu ids visible for the session user.
:return: the root menu ids
:rtype: list(int)
"""
s = request.session
Menus = s.model('ir.ui.menu')
menu_domain = [('parent_id', '=', False)]
return Menus.search(menu_domain, 0, False, False, request.context)
@http.route('/web/menu/load', type='json', auth="user")
def load(self):
""" Loads all menu items (all applications and their sub-menus).
:return: the menu root
:rtype: dict('children': menu_nodes)
"""
Menus = request.session.model('ir.ui.menu')
fields = ['name', 'sequence', 'parent_id', 'action']
menu_root_ids = self.get_user_roots()
menu_roots = Menus.read(menu_root_ids, fields, request.context) if menu_root_ids else []
menu_root = {
'id': False,
'name': 'root',
'parent_id': [-1, ''],
'children': menu_roots,
'all_menu_ids': menu_root_ids,
}
if not menu_roots:
return menu_root
# menus are loaded fully unlike a regular tree view, cause there are a
# limited number of items (752 when all 6.1 addons are installed)
menu_ids = Menus.search([('id', 'child_of', menu_root_ids)], 0, False, False, request.context)
menu_items = Menus.read(menu_ids, fields, request.context)
# adds roots at the end of the sequence, so that they will overwrite
# equivalent menu items from full menu read when put into id:item
# mapping, resulting in children being correctly set on the roots.
menu_items.extend(menu_roots)
menu_root['all_menu_ids'] = menu_ids # includes menu_root_ids!
# make a tree using parent_id
menu_items_map = dict(
(menu_item["id"], menu_item) for menu_item in menu_items)
for menu_item in menu_items:
if menu_item['parent_id']:
parent = menu_item['parent_id'][0]
else:
parent = False
if parent in menu_items_map:
menu_items_map[parent].setdefault(
'children', []).append(menu_item)
# sort by sequence a tree using parent_id
for menu_item in menu_items:
menu_item.setdefault('children', []).sort(
key=operator.itemgetter('sequence'))
return menu_root
@http.route('/web/menu/load_needaction', type='json', auth="user")
def load_needaction(self, menu_ids):
""" Loads needaction counters for specific menu ids.

View File

@ -366,7 +366,6 @@
// change - shortcut for .bind("change", handler) or .trigger("change")
fn.change = function change(handler) {
console.log('change test');
var $this = $(this);
return handler ? $this.bind(CHANGE, handler) : $this.trigger(CHANGE);
};

View File

@ -20,6 +20,25 @@
font-style: normal;
}
#oe_main_menu_navbar {
min-height: 34px;
z-index: 1001;
border-radius: 0;
}
#oe_main_menu_navbar li a, #oe_main_menu_navbar li button {
padding: 4px 8px 4px 8px;
margin-top: 2px;
font-size: 13px;
}
#oe_main_menu_navbar .oe_topbar_avatar {
max-height: 18px;
width: auto;
}
#oe_main_menu_navbar.navbar {
margin-bottom: 0px;
}
.openerp {
padding: 0;
margin: 0;
@ -32,7 +51,12 @@
*/
}
.openerp.openerp_webclient_container {
height: 100%;
height: calc(100% - 34px);
}
@media (max-width: 768px) {
.openerp.openerp_webclient_container {
height: calc(100% - 52px);
}
}
.openerp :-moz-placeholder {
color: #afafb6 !important;
@ -461,7 +485,7 @@
background-image: -webkit-radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
background-image: -moz-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
background-image: -ms-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
background-image: radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
background-image: radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
}
.openerp .oe_kanban_status_green {
background: green;
@ -469,7 +493,7 @@
background-image: -webkit-radial-gradient(circle, #55dd55 0%, #44aa44 40%, #339933 100%);
background-image: -moz-radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
background-image: -ms-radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
background-image: radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
background-image: radial-gradient(circle, #55dd55 0%, #44aa44 40%, #339933 100%);
}
.openerp .oe_kanban_status_red {
background: red;
@ -477,7 +501,7 @@
background-image: -webkit-radial-gradient(circle, #ee7777 0%, #cc3333 40%, #bb0808 100%);
background-image: -moz-radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
background-image: -ms-radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
background-image: radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
background-image: radial-gradient(circle, #ee7777 0%, #cc3333 40%, #bb0808 100%);
}
.openerp .btn-group.kanban_state {
padding-top: 8px;
@ -822,7 +846,7 @@
background-image: -moz-linear-gradient(top, #fc8787, maroon);
background-image: -ms-linear-gradient(top, #fc8787, maroon);
background-image: -o-linear-gradient(top, #fc8787, maroon);
background-image: linear-gradient(to bottom, #fc8787, maroon);
background-image: linear-gradient(to bottom, #fc8787, #800000);
}
.openerp .navbar .oe_topbar_anonymous_login a {
display: block;
@ -925,49 +949,8 @@
white-space: nowrap;
overflow: hidden;
}
.openerp .navbar-nav {
float: left;
padding: 0;
margin: 0;
font-size: 13px;
}
.openerp .navbar-nav > li {
list-style-type: none;
padding: 0;
margin: 0;
float: left;
display: block;
color: #eeeeee;
}
.openerp .navbar-nav > li > a {
display: block;
padding: 5px 10px 26px !important;
line-height: 20px;
height: 20px;
text-decoration: none;
color: #eeeeee !important;
vertical-align: top;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
-webkit-transition: all 0.2s ease-out;
-moz-transition: all 0.2s ease-out;
-ms-transition: all 0.2s ease-out;
-o-transition: all 0.2s ease-out;
transition: all 0.2s ease-out;
}
.openerp .navbar-nav > li > a:hover {
background: rgba(0, 0, 0, 0.2);
text-shadow: black 0px 0px 3px;
color: white;
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset;
}
.openerp .navbar-nav > li > .active {
background: rgba(0, 0, 0, 0.3);
text-shadow: black 0px 0px 3px;
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset;
.openerp .nav li a {
padding: 6px 8px 4px 8px;
}
.openerp .oe_user_menu {
float: right;
@ -981,13 +964,6 @@
.openerp .oe_user_menu .oe_dropdown_menu {
right: -1px;
}
.openerp .oe_systray > div {
float: left;
padding: 0 4px 0 4px;
}
.openerp .oe_systray {
float: right;
}
.openerp .oe_leftbar {
display: none;
width: 220px;
@ -2978,34 +2954,6 @@
font-size: 95%;
line-height: 1.2em;
}
.openerp .navbar {
min-height: 32px;
margin-bottom: 0px;
border: none;
z-index: 1;
position: static;
background-color: #414141;
background-color: #454343;
background-image: -webkit-gradient(linear, left top, left bottom, from(#646060), to(#262626));
background-image: -webkit-linear-gradient(top, #646060, #262626);
background-image: -moz-linear-gradient(top, #646060, #262626);
background-image: -ms-linear-gradient(top, #646060, #262626);
background-image: -o-linear-gradient(top, #646060, #262626);
background-image: linear-gradient(to bottom, #646060, #262626);
}
.openerp .navbar-default .navbar-nav li a:hover, .openerp .navbar-default .navbar-nav li a:focus {
background: rgba(0, 0, 0, 0.3);
}
.openerp .navbar-default .navbar-nav .open > a, .openerp .navbar-default .navbar-nav a:hover, .openerp .navbar-default .navbar-nav a:focus {
background: rgba(0, 0, 0, 0.3) !important;
}
.openerp .navbar-default .navbar-nav .dropdown > a .caret {
border-top-color: #777777 !important;
border-bottom-color: #777777 !important;
}
.openerp .navbar-nav li a {
padding: 4px 32px 4px 12px;
}
.openerp .oe_navbar .dropdown-menu {
font-size: 13px;
padding: 4px 0;
@ -3035,11 +2983,11 @@
.openerp .oe_view_manager_new .oe_form_nosheet .oe_form_label {
font-weight: normal;
}
.openerp .nav li > a {
padding: 3px 4px 2px 18px;
.openerp .nav-pills li > a {
padding: 6px 4px 2px 18px;
color: #4c4c4c;
}
.openerp .nav nav-pills.nav-stacked > li > ul {
.openerp .nav-pills .nav-stacked > li > ul {
padding-left: 16px;
}
.openerp .nav-pills > li.active > a, .openerp a.list-group-item.active > a {

View File

@ -55,7 +55,7 @@ $sheet-padding: 16px
background-image: -webkit-radial-gradient(circle, $gradient)
background-image: -moz-radial-gradient($gradient)
background-image: -ms-radial-gradient($gradient)
background-image: radial-gradient($gradient)
background-image: radial-gradient(circle, $gradient)
@mixin radius($radius: 5px)
-moz-border-radius: $radius
@ -143,6 +143,21 @@ $sheet-padding: 16px
// }}}
#oe_main_menu_navbar
min-height: 34px
z-index: 1001
border-radius: 0
li a, li button
padding: 4px 8px 4px 8px
margin-top: 2px
font-size: 13px
.oe_topbar_avatar
max-height: 18px
width: auto
#oe_main_menu_navbar.navbar
margin-bottom: 0px
.openerp
// Global style {{{
padding: 0
@ -152,7 +167,10 @@ $sheet-padding: 16px
font-size: 13px
background: white
&.openerp_webclient_container
height: 100%
height: calc(100% - 34px)
@media (max-width: 768px)
&.openerp_webclient_container
height: calc(100% - 52px)
// }}}
//Placeholder style{{{
\:-moz-placeholder
@ -713,7 +731,6 @@ $sheet-padding: 16px
display: none
// }}}
// Top Menu {{{
.navbar
.oe_topbar_anonymous_login
background-color: #dc5f59
@ -788,37 +805,8 @@ $sheet-padding: 16px
// So why aren't the buttons oe_topbar_items ? This sad state of affairs
// is a leftover from an uncomplete refactoring when the left and top menu
// were merged. You are welcome to refactor and clean this up
.navbar-nav
float: left
padding: 0
margin: 0
font-size: 13px
> li
list-style-type: none
padding: 0
margin: 0
float: left
display: block
color: #eeeeee
> a
display: block
padding: 5px 10px 26px !important
line-height: 20px
height: 20px
text-decoration: none
color: #eee !important
vertical-align: top
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
@include transition(all 0.2s ease-out)
&:hover
background: rgba(0,0,0,0.2)
text-shadow: black 0px 0px 3px
color: white
@include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset)
> .active
background: rgba(0,0,0,0.30)
text-shadow: black 0px 0px 3px
@include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset)
.nav li a
padding: 6px 8px 4px 8px
.oe_user_menu
float: right
@ -830,12 +818,6 @@ $sheet-padding: 16px
.oe_dropdown_menu
right: -1px
.oe_systray > div
float: left
padding: 0 4px 0 4px
.oe_systray
float: right
// }}}
// Webclient.leftbar {{{
.oe_leftbar
@ -2396,28 +2378,6 @@ $sheet-padding: 16px
line-height: 1.2em
// }}}
// Bootstrap HACKS {{{
.navbar
min-height: 32px
margin-bottom: 0px
border: none
z-index: 1
position: static
background-color: #414141
@include vertical-gradient(#646060, #262626)
.navbar-default
.navbar-nav
li
a:hover,a:focus
background: rgba(0, 0, 0, 0.3)
.open > a,a:hover,a:focus
background: rgba(0, 0, 0, 0.3) !important
.dropdown > a .caret
border-top-color: #777 !important
border-bottom-color: #777 !important
.navbar-nav
li
a
padding: 4px 32px 4px 12px
.oe_navbar .dropdown-menu
font-size: 13px
padding: 4px 0
@ -2440,11 +2400,11 @@ $sheet-padding: 16px
font-weight: normal
// Customize bootstrap3 for leftbar
.nav
.nav-pills
li > a
padding: 3px 4px 2px 18px
padding: 6px 4px 2px 18px
color: #4c4c4c
nav-pills.nav-stacked > li > ul
.nav-stacked > li > ul
padding-left: 16px
.nav-pills > li.active,a.list-group-item.active
> a

View File

@ -734,51 +734,52 @@ instance.web.ChangePassword = instance.web.Widget.extend({
instance.web.client_actions.add("change_password", "instance.web.ChangePassword");
instance.web.Menu = instance.web.Widget.extend({
template: 'Menu',
init: function() {
var self = this;
this._super.apply(this, arguments);
this.has_been_loaded = $.Deferred();
this.is_bound = $.Deferred();
this.maximum_visible_links = 'auto'; // # of menu to show. 0 = do not crop, 'auto' = algo
this.data = {data:{children:[]}};
this.on("menu_loaded", this, function (menu_data) {
self.reflow();
this.on("menu_bound", this, function() {
// launch the fetch of needaction counters, asynchronous
if (!_.isEmpty(menu_data.all_menu_ids)) {
this.do_load_needaction(menu_data.all_menu_ids);
var $all_menus = self.$el.parents('body').find('.oe_webclient').find('[data-menu]');
var all_menu_ids = _.map($all_menus, function (menu) {return parseInt($(menu).attr('data-menu'), 10);});
if (!_.isEmpty(all_menu_ids)) {
this.do_load_needaction(all_menu_ids);
}
});
var lazyreflow = _.debounce(this.reflow.bind(this), 200);
instance.web.bus.on('resize', this, function() {
self.$el.height(0);
lazyreflow();
});
},
start: function() {
this._super.apply(this, arguments);
this.$secondary_menus = this.getParent().$el.find('.oe_secondary_menus_container');
this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
return this.do_reload();
return this.bind_menu();
},
do_reload: function() {
var self = this;
return this.rpc("/web/menu/load", {}).done(function(r) {
self.menu_loaded(r);
});
self.bind_menu();
},
menu_loaded: function(data) {
bind_menu: function() {
var self = this;
this.data = {data: data};
this.renderElement();
this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this }));
this.$secondary_menus = this.$el.parents().find('.oe_secondary_menus_container')
this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
this.$el.on('click', 'a[data-menu]', this.on_top_menu_click);
// Hide second level submenus
this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide();
if (self.current_menu) {
self.open_menu(self.current_menu);
}
this.trigger('menu_loaded', data);
this.has_been_loaded.resolve();
this.trigger('menu_bound');
var lazyreflow = _.debounce(this.reflow.bind(this), 200);
instance.web.bus.on('resize', this, function() {
if (parseInt(self.$el.parent().css('width')) <= 768 ) {
lazyreflow('all_outside');
} else {
lazyreflow();
}
});
instance.web.bus.trigger('resize');
this.is_bound.resolve();
},
do_load_needaction: function (menu_ids) {
var self = this;
@ -803,30 +804,48 @@ instance.web.Menu = instance.web.Widget.extend({
},
/**
* Reflow the menu items and dock overflowing items into a "More" menu item.
* Automatically called when 'menu_loaded' event is triggered and on window resizing.
* Automatically called when 'menu_bound' event is triggered and on window resizing.
*
* @param {string} behavior If set to 'all_outside', all the items are displayed. If set to
* 'all_inside', all the items are hidden under the more item. If not set, only the
* overflowing items are hidden.
*/
reflow: function() {
reflow: function(behavior) {
var self = this;
this.$el.height('auto').show();
var $more_container = this.$('#menu_more_container').hide();
var $more = this.$('#menu_more');
$more.children('li').insertBefore($more_container);
var $toplevel_items = this.$el.children('li').not($more_container).hide();
var $systray = this.$el.parents().find('.oe_systray');
$more.children('li').insertBefore($more_container); // Pull all the items out of the more menu
// 'all_outside' beahavior should display all the items, so hide the more menu and exit
if (behavior === 'all_outside') {
this.$el.find('li').show();
$more_container.hide();
return;
}
var $toplevel_items = this.$el.find('li').not($more_container).not($systray.find('li')).hide();
$toplevel_items.each(function() {
// In all inside mode, we do not compute to know if we must hide the items, we hide them all
if (behavior === 'all_inside') {
return false;
}
var remaining_space = self.$el.parent().width() - $more_container.outerWidth();
self.$el.parent().children(':visible').each(function() {
remaining_space -= $(this).outerWidth();
});
if ($(this).width() > remaining_space) {
return false;
}
$(this).show();
});
$more.append($toplevel_items.filter(':hidden').show());
$more_container.toggle(!!$more.children().length);
$more_container.toggle(!!$more.children().length || behavior === 'all_inside');
// Hide toplevel item if there is only one
var $toplevel = this.$el.children("li:visible");
if ($toplevel.length === 1) {
if ($toplevel.length === 1 && behavior != 'all_inside') {
$toplevel.hide();
}
},
@ -853,7 +872,7 @@ instance.web.Menu = instance.web.Widget.extend({
// Activate current main menu
this.$el.find('.active').removeClass('active');
$main_menu.addClass('active');
$main_menu.parent().addClass('active');
// Show current sub menu
this.$secondary_menus.find('.oe_secondary_menu').hide();
@ -939,22 +958,19 @@ instance.web.Menu = instance.web.Widget.extend({
* @param {Event} ev the jquery event
*/
on_top_menu_click: function(ev) {
ev.preventDefault();
var self = this;
var id = $(ev.currentTarget).data('menu');
var menu_ids = [id];
var menu = _.filter(this.data.data.children, function (menu) {return menu.id == id;})[0];
function add_menu_ids (menu) {
if (menu.children) {
_.each(menu.children, function (menu) {
menu_ids.push(menu.id);
add_menu_ids(menu);
});
}
}
add_menu_ids(menu);
// Fetch the menu leaves ids in order to check if they need a 'needaction'
var $secondary_menu = this.$el.parents().find('.oe_secondary_menu[data-menu-parent=' + id + ']');
var $menu_leaves = $secondary_menu.children().find('.oe_menu_leaf');
var menu_ids = _.map($menu_leaves, function (leave) {return parseInt($(leave).attr('data-menu'), 10);});
self.do_load_needaction(menu_ids).then(function () {
self.trigger("need_action_reloaded");
});
this.on_menu_click(ev);
},
on_menu_click: function(ev) {
@ -980,6 +996,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
f($(this));
}
});
this.$el.parent().show()
},
do_update: function () {
var self = this;
@ -1001,6 +1018,8 @@ instance.web.UserMenu = instance.web.Widget.extend({
}
var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid});
$avatar.attr('src', avatar_src);
openerp.web.bus.trigger('resize'); // Re-trigger the reflow logic
});
};
this.update_promise = this.update_promise.then(fct, fct);
@ -1086,15 +1105,13 @@ instance.web.Client = instance.web.Widget.extend({
start: function() {
var self = this;
return instance.session.session_bind(this.origin).then(function() {
var $e = $(QWeb.render(self._template, {widget: self}));
self.replaceElement($e);
$e.openerpClass();
self.bind_events();
return self.show_common();
});
},
bind_events: function() {
var self = this;
$('.oe_systray').show();
this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function() {
$(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
});
@ -1146,10 +1163,6 @@ instance.web.Client = instance.web.Widget.extend({
});
instance.web.WebClient = instance.web.Client.extend({
_template: 'WebClient',
events: {
'click .oe_logo_edit_admin': 'logo_edit'
},
init: function(parent, client_options) {
this._super(parent);
if (client_options) {
@ -1164,6 +1177,7 @@ instance.web.WebClient = instance.web.Client.extend({
var self = this;
this.on("change:title_part", this, this._title_changed);
this._title_changed();
return $.when(this._super()).then(function() {
if (jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
self.to_kitten();
@ -1227,12 +1241,19 @@ instance.web.WebClient = instance.web.Client.extend({
show_application: function() {
var self = this;
self.toggle_bars(true);
self.update_logo();
this.$('.oe_logo_edit_admin').click(function(ev) {
self.logo_edit(ev);
});
// Menu is rendered server-side thus we don't want the widget to create any dom
self.menu = new instance.web.Menu(self);
self.menu.replace(this.$el.find('.oe_menu_placeholder'));
self.menu.setElement(this.$el.parents().find('.oe_application_menu_placeholder'));
self.menu.start();
self.menu.on('menu_click', this, this.on_menu_action);
self.user_menu = new instance.web.UserMenu(self);
self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder'));
self.user_menu.appendTo(this.$el.parents().find('.oe_user_menu_placeholder'));
self.user_menu.on('user_logout', self, self.on_logout);
self.user_menu.do_update();
self.bind_hashchange();
@ -1250,6 +1271,7 @@ instance.web.WebClient = instance.web.Client.extend({
},
logo_edit: function(ev) {
var self = this;
ev.preventDefault();
self.alive(new instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) {
result.res_id = res['company_id'][0];
@ -1294,7 +1316,7 @@ instance.web.WebClient = instance.web.Client.extend({
return false;
});
});
$icon.appendTo(self.$('.oe_systray'));
$icon.prependTo(window.$('.oe_systray'));
}
});
},
@ -1309,7 +1331,6 @@ instance.web.WebClient = instance.web.Client.extend({
return this.session.session_reload().then(function () {
instance.session.load_modules(true).then(
self.menu.proxy('do_reload')); });
},
do_notify: function() {
var n = this.notification;
@ -1331,15 +1352,16 @@ instance.web.WebClient = instance.web.Client.extend({
var state = $.bbq.getState(true);
if (_.isEmpty(state) || state.action == "login") {
self.menu.has_been_loaded.done(function() {
self.menu.is_bound.done(function() {
new instance.web.Model("res.users").call("read", [self.session.uid, ["action_id"]]).done(function(data) {
if(data.action_id) {
self.action_manager.do_action(data.action_id[0]);
self.menu.open_action(data.action_id[0]);
} else {
var first_menu_id = self.menu.$el.find("a:first").data("menu");
if(first_menu_id)
if(first_menu_id) {
self.menu.menu_click(first_menu_id);
}
}
});
});
@ -1353,10 +1375,9 @@ instance.web.WebClient = instance.web.Client.extend({
if (!_.isEqual(this._current_state, stringstate)) {
var state = event.getState(true);
if(!state.action && state.menu_id) {
self.menu.has_been_loaded.done(function() {
self.menu.do_reload().done(function() {
self.menu.menu_click(state.menu_id);
});
self.menu.is_bound.done(function() {
self.menu.do_reload();
self.menu.menu_click(state.menu_id);
});
} else {
state._push_me = false; // no need to push state back...

View File

@ -271,7 +271,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
action_loaded = this.do_action(state.action, { additional_context: add_context });
$.when(action_loaded || null).done(function() {
instance.webclient.menu.has_been_loaded.done(function() {
instance.webclient.menu.is_bound.done(function() {
if (self.inner_action && self.inner_action.id) {
instance.webclient.menu.open_action(self.inner_action.id);
}

View File

@ -294,9 +294,9 @@
</div>
</t>
<t t-name="DatabaseManager.user_menu">
<span class="oe_right">
<a id="back-to-login" href="#"><span class="oe_topbar_item oe_topbar_name">Back to Login</span></a>
</span>
<li>
<a id="back-to-login" href="#">Back to Login</a>
</li>
</t>
<t t-name="DatabaseManager.menu">
<div class="oe_secondary_menu_section">Database Management</div>
@ -341,52 +341,6 @@
</form>
</t>
<t t-name="Menu">
<ul class="nav navbar-nav navbar-left" t-if="widget.data">
<li t-foreach="widget.data.data.children" t-as="menu">
<t t-call="Menu.link"/>
</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>
</t>
<t t-name="Menu.secondary">
<div t-foreach="widget.data.data.children" t-as="menu" style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu.id">
<t t-foreach="menu.children" t-as="menu">
<div class="oe_secondary_menu_section">
<t t-esc="menu.name"/>
<!--
Shall the section be still clickable ?
<t t-call="Menu.link"/>
-->
</div>
<t t-call="Menu.secondary.submenu"/>
</t>
</div>
</t>
<t t-name="Menu.secondary.submenu">
<ul t-if="menu.children.length" class="oe_secondary_submenu nav nav-pills nav-stacked">
<li t-foreach="menu.children" t-as="menu">
<t t-call="Menu.link"/>
<!--<span class="oe_menu_label">8</span>-->
<t t-call="Menu.secondary.submenu"/>
</li>
</ul>
</t>
<t t-name="Menu.link">
<a t-attf-href="#menu_id=#{menu.id}&amp;action=#{menu.action ? menu.action.split(',')[1] : ''}"
t-att-class="menu.children.length ? 'oe_menu_toggler' : 'oe_menu_leaf'"
t-att-data-menu="menu.id"
t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''"
t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''">
<span class="oe_menu_text">
<t t-esc="menu.name"/>
</span>
</a>
</t>
<t t-name="Menu.needaction_counter">
<div id="menu_counter" class="badge pull-right">
<t t-if="widget.needaction_counter &gt; 99"> 99+ </t>
@ -395,7 +349,6 @@
</t>
<t t-name="UserMenu">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<img class="oe_topbar_avatar" t-att-data-default-src="_s + '/web/static/src/img/user_menu_avatar.png'"/>
@ -409,7 +362,6 @@
<li><a href="#" data-menu="logout">Log out</a></li>
</ul>
</li>
</ul>
</t>
<t t-name="UserMenu.about">
@ -428,40 +380,6 @@
</t>
<t t-name="WebClient">
<div class="openerp openerp_webclient_container">
<table class="oe_webclient">
<tr>
<td colspan="2" class="navbar navbar-default navbar-static-top oe_navbar" role="navigation">
<div class="oe_menu_placeholder"/>
<div class="oe_user_menu_placeholder"/>
<div class="oe_systray"/>
</td>
</tr>
<tr>
<td class="oe_leftbar" valign="top">
<div>
<t t-set="debug" t-value="__debug__ ? '&amp;debug' : ''"/>
<a class="oe_logo" t-attf-href="/web?#{debug}">
<span class="oe_logo_edit">Edit Company data</span>
<img t-att-src='widget.session.url("/web/binary/company_logo")'/>
</a>
<div>
<div>
<div class="oe_secondary_menus_container"/>
</div>
</div>
<div class="oe_footer">
Powered by <a href="http://www.odoo.com" target="_blank"><span>Odoo</span></a>
</div>
</div>
</td>
<td class="oe_application">
</td>
</tr>
</table>
</div>
</t>
<t t-name="WebClient.timezone_notification">
<div class="oe_webclient_timezone_notification">
<p>Your user's preference timezone does not match your browser timezone:</p>
@ -477,9 +395,9 @@
</div>
</t>
<t t-name="WebClient.timezone_systray">
<div class="oe_topbar_item oe_timezone_systray" title="Timezone mismatch">
<span class="ui-icon ui-state-error ui-icon-alert"/>
</div>
<li class="oe_topbar_item oe_timezone_systray" title="Timezone mismatch">
<a href="#"><i class="fa fa-exclamation-triangle" style="color:red;"></i></a>
</li>
</t>
<t t-name="EmbedClient">

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
import test_js
import test_menu
# import test_menu
import test_serving_base

View File

@ -10,15 +10,17 @@ from . import common
from ..controllers import main
class Placeholder(object):
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
setattr(self, k, v)
class LoadTest(common.MockRequestCase):
def setUp(self):
super(LoadTest, self).setUp()
self.menu = main.Menu()
self.home = main.Home()
# Have self.request.session.model() return a different mock object for
# each model (but always the same mock for a given model name)
@ -32,17 +34,17 @@ class LoadTest(common.MockRequestCase):
def tearDown(self):
del self.MockMenus
del self.menu
del self.home
super(LoadTest, self).tearDown()
def test_empty(self):
self.MockMenus.search.return_value = []
self.MockMenus.read.return_value = []
root = self.menu.load()
root = self.home.load_menus()
self.MockMenus.search.assert_called_with(
[('parent_id','=', False)], 0, False, False,
[('parent_id', '=', False)], 0, False, False,
req.context)
self.assertEqual(root['all_menu_ids'], [])
@ -59,10 +61,10 @@ class LoadTest(common.MockRequestCase):
{'id': 2, 'sequence': 3, 'parent_id': False},
]
root = self.menu.load()
root = self.home.load_menus()
self.MockMenus.search.assert_called_with(
[('id','child_of', [1, 2, 3])], 0, False, False,
[('id', 'child_of', [1, 2, 3])], 0, False, False,
req.context)
self.MockMenus.read.assert_called_with(
@ -98,10 +100,10 @@ class LoadTest(common.MockRequestCase):
{'id': 4, 'sequence': 2, 'parent_id': [2, '']},
])
root = self.menu.load()
root = self.home.load_menus()
self.MockMenus.search.assert_called_with(
[('id','child_of', [1])], 0, False, False,
[('id', 'child_of', [1])], 0, False, False,
req.context)
self.assertEqual(root['all_menu_ids'], [1, 2, 3, 4])
@ -131,9 +133,8 @@ class LoadTest(common.MockRequestCase):
}]
)
class ActionMungerTest(unittest2.TestCase):
def setUp(self):
self.menu = main.Menu()
def test_actual_treeview(self):
action = {
"views": [[False, "tree"], [False, "form"],
@ -166,7 +167,6 @@ class ActionMungerTest(unittest2.TestCase):
})
def test_redundant_views(self):
action = {
"views": [[False, "tree"], [False, "form"],
[False, "calendar"], [42, "tree"]],

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html style="height: 100%">
<html style="height: 100%;">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
@ -67,11 +67,51 @@
$(function() {
var s = new openerp.init({{ modules|safe }});
var wc = new s.web.WebClient(null, { action: 'database_manager' });
wc.appendTo($(document.body));
wc.setElement($(document.body));
wc.start();
});
</script>
</head>
<body>
<nav id="oe_main_menu_navbar" class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse" id="oe_main_menu_placeholder">
<ul class="nav navbar-nav navbar-right oe_user_menu_placeholder"></ul>
</div>
</nav>
<div class="openerp openerp_webclient_container">
<table class="oe_webclient">
<tr>
<td class="oe_leftbar" valign="top">
<div>
<a class="oe_logo">
<span class="oe_logo_edit">Edit Company data</span>
<img src='/web/binary/company_logo'/>
</a>
<div>
<div>
<div class="oe_secondary_menus_container"></div>
</div>
</div>
<div class="oe_footer">
Powered by <a href="http://www.odoo.com" target="_blank"><span>Odoo</span></a>
</div>
</div>
</td>
<td class="oe_application">
</td>
</tr>
</table>
</div>
<!--[if lte IE 8]>
<script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script>CFInstall.check({mode: "overlay"});</script>

View File

@ -134,6 +134,66 @@
</html>
</template>
<template id="web.menu">
<ul class="nav navbar-nav navbar-left oe_application_menu_placeholder">
<li t-foreach="menu_data['children']" t-as="menu">
<t t-call="web.menu_link"/>
</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>
<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;"/>
</template>
<template id="web.menu_secondary">
<a class="oe_logo" t-att-href="'/web/?debug' if debug else '/web'">
<span class="oe_logo_edit">Edit Company data</span>
<img src='/web/binary/company_logo'/>
</a>
<div>
<div>
<div class="oe_secondary_menus_container">
<t t-foreach="menu_data['children']" t-as="menu">
<div style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu['id']">
<t t-foreach="menu['children']" t-as="menu">
<div class="oe_secondary_menu_section">
<t t-esc="menu['name']"/>
</div>
<t t-call="web.menu_secondary_submenu"/>
</t>
</div>
</t>
</div>
</div>
</div>
<div class="oe_footer">
Powered by <a href="http://www.openerp.com" target="_blank"><span>Odoo</span></a>
</div>
</template>
<template id="web.menu_secondary_submenu">
<ul t-if="menu['children']" class="oe_secondary_submenu nav nav-pills nav-stacked">
<li t-foreach="menu['children']" t-as="menu">
<t t-call="web.menu_link"/>
<t t-call="web.menu_secondary_submenu"/>
</li>
</ul>
</template>
<template id="web.menu_link">
<t t-set="debug_param" t-value="'?&amp;debug=' if debug else ''"/>
<a t-att-href="'/web%s#menu_id=%s&amp;action=%s' % (debug_param, menu['id'], menu['action'] and menu['action'].split(',')[1] or '')"
t-att-class="'oe_menu_toggler' if menu['children'] else 'oe_menu_leaf'"
t-att-data-menu="menu['id']"
t-att-data-action-model="menu['action'] and menu['action'].split(',')[0] or ''"
t-att-data-action-id="menu['action'] and menu['action'].split(',')[1] or ''">
<span class="oe_menu_text">
<t t-esc="menu['name']"/>
</span>
</a>
</template>
<template id="web.webclient_bootstrap" name="Webclient Bootstrap">
<t t-call="web.layout">
<t t-set="head">
@ -147,11 +207,37 @@
</t>
<t t-if="not init">
var wc = new s.web.WebClient();
wc.appendTo($(document.body));
wc.setElement($(document.body));
wc.start();
</t>
});
</script>
</t>
<nav id="oe_main_menu_navbar" class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse" id="oe_main_menu_placeholder">
<t t-call="web.menu"/>
</div>
</nav>
<div class="openerp openerp_webclient_container">
<table class="oe_webclient">
<tr>
<td class="oe_leftbar" valign="top">
<div>
<t t-call="web.menu_secondary"/>
</div>
</td>
<td class="oe_application"/>
</tr>
</table>
</div>
</t>
</template>

View File

@ -21,6 +21,7 @@ OpenERP Website CMS
'views/themes.xml',
'views/res_config.xml',
'views/ir_actions.xml',
'views/website_backend_navbar.xml',
],
'demo': [
'data/demo.xml',

View File

@ -151,6 +151,7 @@ class view(osv.osv):
user_id=self.pool.get("res.users").browse(cr, uid, uid),
translatable=context.get('lang') != request.website.default_lang_code,
editable=request.website.is_publisher(),
menu_data=self.pool['ir.ui.menu'].load_menus(cr, uid, context=context) if request.website.is_user() else None,
)
# add some values

View File

@ -243,6 +243,9 @@ class website(osv.osv):
is_website_publisher = Access.check(cr, uid, 'ir.ui.view', 'write', False, context)
return is_website_publisher
def is_user(self, cr, uid, ids, context=None):
return self.pool['res.users'].has_group(cr, request.uid, 'base.group_user')
def get_template(self, cr, uid, ids, template, context=None):
if isinstance(template, (int, long)):
view_id = template

View File

@ -29,6 +29,24 @@
-ms-filter: "alpha(opacity=50)";
}
#cke_1_top {
padding: 0;
}
#cke_wrapwrap {
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#cke_wrapwrap .cke_button {
padding-top: 5px;
padding-bottom: 5px;
}
#cke_wrapwrap .cke_combo_button {
padding-top: 1px;
padding-bottom: 1px;
}
/* ---- OpenERP Style ---- {{{ */
.oe_website_editorbar {
position: fixed;
@ -115,6 +133,26 @@
height: 16px !important;
}
#website-top-edit {
width: 100%;
}
#website-top-edit > ul > li {
margin: 0;
}
#website-top-navbar {
min-height: 34px;
height: 34px;
}
#website-top-navbar form {
margin: 0;
}
#website-top-navbar form button, #website-top-navbar form a {
padding: 4px 8px 4px 8px;
margin-top: 2px;
font-size: 13px;
}
/* ---- EDITOR BAR ---- {{{ */
table.editorbar-panel {
cursor: pointer;
@ -470,7 +508,7 @@ ul.oe_menu_editor .disclose {
.oe_ace_view_editor {
position: fixed;
right: 0;
z-index: 1000;
z-index: 1001;
height: 100%;
background: #2f3129;
color: white;

View File

@ -25,6 +25,20 @@
+box-shadow(none)
-ms-filter: "alpha(opacity=50)"
#cke_1_top
padding: 0
#cke_wrapwrap
-moz-box-shadow: none
-webkit-box-shadow: none
box-shadow: none
.cke_button
padding-top: 5px
padding-bottom: 5px
.cke_combo_button
padding-top: 1px
padding-bottom: 1px
// }}}
/* ---- OpenERP Style ---- {{{ */
@ -93,6 +107,21 @@
background-image: none !important
height: 16px !important
#website-top-edit
width: 100%
> ul > li
margin: 0
#website-top-navbar
min-height: 34px
height: 34px
form
margin: 0
button, a
padding: 4px 8px 4px 8px
margin-top: 2px
font-size: 13px
// }}}
/* ---- EDITOR BAR ---- {{{ */
@ -415,7 +444,7 @@ $infobar_height: 20px
position: fixed
// top property is set programmatically
right: 0
z-index: 1000
z-index: 1001
height: 100%
background: #2F3129
color: white

View File

@ -1,8 +1,10 @@
/* ---- SNIPPET EDITOR ---- {{{ */
#oe_snippets {
position: fixed;
top: 34px;
left: 0px;
right: 0px;
padding-top: 6px;
background: #282828;
-webkit-box-shadow: 0px 10px 10px -10px black inset;
-moz-box-shadow: 0px 10px 10px -10px black inset;

View File

@ -7,8 +7,10 @@
#oe_snippets
position: fixed
top: 34px
left: 0px
right: 0px
padding-top: 6px
// top property is set programmatically
background: rgb(40,40,40)
+box-shadow(0px 10px 10px -10px black inset)

View File

@ -120,8 +120,27 @@ header a.navbar-brand img {
}
/* ----- EDITOR ----- */
#oe_main_menu_navbar {
min-height: 34px;
z-index: 1001;
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
-ms-border-radius: 0px;
-o-border-radius: 0px;
border-radius: 0px;
margin-bottom: 0px;
}
#oe_main_menu_navbar li a, #oe_main_menu_navbar li button {
padding: 4px 8px 4px 8px;
margin-top: 2px;
font-size: 13px;
}
#oe_main_menu_navbar .navbar-nav.navbar-right:last-child {
margin-right: 0 !important;
}
.css_non_editable_mode_hidden {
display: none !important;
display: none;
}
/* ----- BOOTSTRAP FIX ----- */

View File

@ -86,6 +86,17 @@ header
margin-right: auto
/* ----- EDITOR ----- */
#oe_main_menu_navbar
min-height: 34px
z-index: 1001
+border-radius(0px)
margin-bottom: 0px
li a, li button
padding: 4px 8px 4px 8px
margin-top: 2px
font-size: 13px
.navbar-nav.navbar-right:last-child
margin-right: 0 !important
.css_non_editable_mode_hidden
display: none

View File

@ -7,19 +7,7 @@
var website = openerp.website;
website.add_template_file('/website/static/src/xml/website.ace.xml');
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=ace]': 'launchAce',
}),
start: function () {
var self = this;
this.globalEditor = null;
return this._super.apply(this, arguments).then(function () {
if (window.location.hash.indexOf(hash) >= 0) {
self.launchAce();
}
});
},
website.Ace = openerp.Widget.extend({
launchAce: function (e) {
if (e) {
e.preventDefault();
@ -157,10 +145,10 @@
self.close();
});
this.getParent().on('change:height', this, function (editor) {
resizeEditorHeight(editor.get('height'));
resizeEditorHeight(this.getParent().$el.outerHeight()+2);
});
resizeEditor(readEditorWidth());
resizeEditorHeight(this.getParent().get('height'));
resizeEditorHeight(this.getParent().$el.outerHeight()+2);
},
loadViews: function (views) {
var $viewList = this.$('#ace-view-list');
@ -368,4 +356,11 @@
},
});
website.ready().done(function() {
var ace = new website.Ace();
$(document.body).on('click', 'a[data-action=ace]', function() {
ace.launchAce();
});
});
})();

View File

@ -2,19 +2,22 @@
'use strict';
var website = openerp.website;
website.menu = {};
website.add_template_file('/website/static/src/xml/website.menu.xml');
website.contentMenu = {};
website.add_template_file('/website/static/src/xml/website.contentMenu.xml');
var _t = openerp._t;
website.EditorBar.include({
website.EditorBarContent = openerp.Widget.extend({
start: function() {
var res = this._super();
this.$(".oe_content_menu a[data-action=edit-structure]").parents("li").removeClass("hidden");
return res;
var self = this;
self.$el.on('click', 'a', function(ev) {
ev.preventDefault();
var $content_item = $(this);
self[$content_item.data('action')]();
})
return this._super();
},
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action="edit-structure"]': 'editStructure',
}),
editStructure: function () {
edit_menu: function() {
var self = this;
var context = website.get_context();
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
@ -25,14 +28,40 @@
context: context
},
}).then(function (menu) {
var result = new website.menu.EditMenuDialog(menu).appendTo(document.body);
var result = new website.contentMenu.EditMenuDialog(menu).appendTo(document.body);
return result;
});
},
new_page: function() {
website.prompt({
id: "editor_new_page",
window_title: _t("New Page"),
input: _t("Page Title"),
init: function () {
var $group = this.$dialog.find("div.form-group");
$group.removeClass("mb0");
var $add = $(
'<div class="form-group mb0">'+
'<label class="col-sm-offset-3 col-sm-9 text-left">'+
' <input type="checkbox" checked="checked" required="required"/> '+
'</label>'+
'</div>');
$add.find('label').append(_t("Add page in menu"));
$group.after($add);
}
}).then(function (val, field, $dialog) {
if (val) {
var url = '/website/add/' + encodeURIComponent(val);
if ($dialog.find('input[type="checkbox"]').is(':checked')) url +="?add_menu=1";
document.location = url;
}
});
}
});
website.menu.EditMenuDialog = website.editor.Dialog.extend({
template: 'website.menu.dialog.edit',
website.contentMenu.EditMenuDialog = website.editor.Dialog.extend({
template: 'website.contentMenu.dialog.edit',
events: _.extend({}, website.editor.Dialog.prototype.events, {
'click a.js_add_menu': 'add_menu',
'click button.js_edit_menu': 'edit_menu',
@ -73,7 +102,7 @@
},
add_menu: function () {
var self = this;
var dialog = new website.menu.MenuEntryDialog();
var dialog = new website.contentMenu.MenuEntryDialog();
dialog.on('add-menu', this, function (link) {
var new_menu = {
id: _.uniqueId('new-'),
@ -86,7 +115,7 @@
};
self.flat[new_menu.id] = new_menu;
self.$('.oe_menu_editor').append(
openerp.qweb.render('website.menu.dialog.submenu', { submenu: new_menu }));
openerp.qweb.render('website.contentMenu.dialog.submenu', { submenu: new_menu }));
});
dialog.appendTo(document.body);
},
@ -95,7 +124,7 @@
var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id');
var menu = self.flat[menu_id];
if (menu) {
var dialog = new website.menu.MenuEntryDialog(undefined, menu);
var dialog = new website.contentMenu.MenuEntryDialog(undefined, menu);
dialog.on('update-menu', this, function (link) {
var id = link.shift();
var menu_obj = self.flat[id];
@ -152,8 +181,8 @@
},
});
website.menu.MenuEntryDialog = website.editor.LinkDialog.extend({
template: 'website.menu.dialog.add',
website.contentMenu.MenuEntryDialog = website.editor.LinkDialog.extend({
template: 'website.contentMenu.dialog.add',
init: function (editor, data) {
this.data = data;
return this._super.apply(this, arguments);
@ -196,4 +225,10 @@
},
});
website.ready().done(function() {
var content = new website.EditorBarContent()
content.setElement($('.oe_content_menu'));
content.start();
});
})();

View File

@ -12,8 +12,14 @@
if (!is_smartphone) {
website.ready().then(website.init_editor);
} else {
// remove padding of fake editor bar
document.body.style.padding = 0;
var resize_smartphone = function () {
is_smartphone = $(document.body)[0].clientWidth < 767;
if (!is_smartphone) {
$(window).off("resize", resize_smartphone);
website.init_editor();
}
};
$(window).on("resize", resize_smartphone);
}
$(document).on('click', 'a.js_link2post', function (ev) {
@ -79,6 +85,7 @@
// elements are used for e.g. support of FontAwesome icons
CKEDITOR.dtd.$removeEmpty = {};
website.init_editor = function () {
CKEDITOR.plugins.add('customdialogs', {
// requires: 'link,image',
@ -435,68 +442,24 @@
website.EditorBar = openerp.Widget.extend({
template: 'website.editorbar',
events: {
'click button[data-action=edit]': 'edit',
'click button[data-action=save]': 'save',
'click a[data-action=cancel]': 'cancel',
},
container: 'body',
customize_setup: function() {
var self = this;
var view_name = $(document.documentElement).data('view-xmlid');
if (!view_name) {
this.$('#customize-menu-button').addClass("hidden");
}
var menu = $('#customize-menu');
this.$('#customize-menu-button').click(function(event) {
menu.empty();
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then(
function(result) {
_.each(result, function (item) {
if (item.header) {
menu.append('<li class="dropdown-header">' + item.name + '</li>');
} else {
menu.append(_.str.sprintf('<li role="presentation"><a href="#" data-view-id="%s" role="menuitem"><strong class="fa fa%s-square-o"></strong> %s</a></li>',
item.id, item.active ? '-check' : '', item.name));
}
});
// Adding Static Menus
menu.append('<li class="divider"></li>');
menu.append('<li><a data-action="ace" href="#">HTML Editor</a></li>');
menu.append('<li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
menu.append('<li><a href="/web#return_label=Website&action=website.action_module_website">Install Apps</a></li>');
self.trigger('rte:customize_menu_ready');
}
);
});
menu.on('click', 'a[data-view-id]', function (event) {
var view_id = $(event.currentTarget).data('view-id');
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'ir.ui.view',
method: 'toggle',
args: [],
kwargs: {
ids: [parseInt(view_id, 10)],
context: website.get_context()
}
}).then( function() {
window.location.reload();
});
});
},
start: function() {
// remove placeholder editor bar
var fakebar = document.getElementById('website-top-navbar-placeholder');
if (fakebar) {
fakebar.parentNode.removeChild(fakebar);
}
var self = this;
this.saving_mutex = new openerp.Mutex();
this.$buttons = {
edit: this.$el.parents().find('button[data-action=edit]'),
save: this.$('button[data-action=save]'),
cancel: this.$('button[data-action=cancel]'),
};
this.$('#website-top-edit').hide();
this.$('#website-top-view').show();
this.$buttons.edit.show();
var $edit_button = this.$('button[data-action=edit]')
var $edit_button = this.$buttons.edit
.prop('disabled', website.no_editor);
if (website.no_editor) {
var help_text = $(document.documentElement).data('editable-no-editor');
@ -506,52 +469,31 @@
.attr('title', help_text);
}
$('.dropdown-toggle').dropdown();
this.customize_setup();
this.$buttons = {
edit: this.$('button[data-action=edit]'),
save: this.$('button[data-action=save]'),
cancel: this.$('button[data-action=cancel]'),
};
this.$buttons.edit.click(function(ev) {
self.edit();
});
this.rte = new website.RTE(this);
this.rte.on('change', this, this.proxy('rte_changed'));
this.rte.on('rte:ready', this, function () {
self.setup_hover_buttons();
self.trigger('rte:ready');
self.check_height();
});
$(window).on('resize', _.debounce(this.check_height.bind(this), 50));
this.check_height();
if (website.is_editable_button) {
this.$("button[data-action=edit]").removeClass("hidden");
}
return $.when(
this._super.apply(this, arguments),
this.rte.appendTo(this.$('#website-top-edit .nav.pull-right'))
).then(function () {
self.check_height();
});
},
check_height: function () {
var editor_height = this.$el.outerHeight();
if (this.get('height') != editor_height) {
$(document.body).css('padding-top', editor_height);
this.set('height', editor_height);
}
this.rte.appendTo(this.$('#website-top-edit .nav.js_editor_placeholder'));
return this._super.apply(this, arguments);
},
edit: function () {
this.$buttons.edit.prop('disabled', true);
this.$('#website-top-view').hide();
this.$el.show();
this.$('#website-top-edit').show();
$('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");
this.rte.start_edition().then(this.check_height.bind(this));
this.rte.start_edition();
this.trigger('rte:called');
},
rte_changed: function () {
@ -761,6 +703,62 @@
});
}
});
website.EditorBarCustomize = openerp.Widget.extend({
events: {
'mousedown a.dropdown-toggle': 'load_menu',
'click ul a[data-action!=ace]': 'do_customize',
},
start: function() {
var self = this;
this.$menu = self.$el.find('ul');
this.view_name = $(document.documentElement).data('view-xmlid');
if (!this.view_name) {
this.$el.hide();
}
this.loaded = false;
},
load_menu: function () {
var self = this;
if(this.loaded) {
return;
}
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': this.view_name }).then(
function(result) {
_.each(result, function (item) {
if (item.xml_id === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
if (item.header) {
self.$menu.append('<li class="dropdown-header">' + item.name + '</li>');
} else {
self.$menu.append(_.str.sprintf('<li role="presentation"><a href="#" data-view-id="%s" role="menuitem"><strong class="fa fa%s-square-o"></strong> %s</a></li>',
item.id, item.active ? '-check' : '', item.name));
}
});
self.loaded = true;
}
);
},
do_customize: function (event) {
var view_id = $(event.currentTarget).data('view-id');
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'ir.ui.view',
method: 'toggle',
args: [],
kwargs: {
ids: [parseInt(view_id, 10)],
context: website.get_context()
}
}).then( function() {
window.location.reload();
});
},
});
website.ready().done(function() {
var editorBarCustomize = new website.EditorBarCustomize();
editorBarCustomize.setElement($('li[id=customize-menu]'));
editorBarCustomize.start();
});
var blocks_selector = _.keys(CKEDITOR.dtd.$block).join(',');
/* ----- RICH TEXT EDITOR ---- */

View File

@ -1,58 +0,0 @@
(function() {
"use strict";
var website = openerp.website;
var _t = openerp._t;
website.is_editable_button = true;
website.EditorBar.include({
start: function() {
var res = this._super();
this.$("a[data-action=new_page]").parents("li").removeClass("hidden");
this.$(".oe_content_menu li.divider").removeClass("hidden");
return res;
},
edit: function () {
this.on('rte:ready', this, function () {
$('a:has(span[data-oe-model="website.menu"])').tooltip({
title: _t('Save this page and use the top "Content" menu to edit the menu.'),
placement: "bottom",
trigger: "hover",
show: 50,
hide: 100,
container: 'body'
});
});
return this._super();
},
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=new_page]': function (ev) {
ev.preventDefault();
website.prompt({
id: "editor_new_page",
window_title: _t("New Page"),
input: _t("Page Title"),
init: function () {
var $group = this.$dialog.find("div.form-group");
$group.removeClass("mb0");
var $add = $(
'<div class="form-group mb0">'+
'<label class="col-sm-offset-3 col-sm-9 text-left">'+
' <input type="checkbox" checked="checked" required="required"/> '+
'</label>'+
'</div>');
$add.find('label').append(_t("Add page in menu"));
$group.after($add);
}
}).then(function (val, field, $dialog) {
if (val) {
var url = '/website/add/' + encodeURIComponent(val);
if ($dialog.find('input[type="checkbox"]').is(':checked')) url +="?add_menu=1";
document.location = url;
}
});
}
}),
});
})();

View File

@ -5,7 +5,6 @@
openerp.website = website;
website.translatable = !!$('html').data('translatable');
website.is_editable = !!$('html').data('editable');
/* ----------------------------------------------------
Helpers
@ -247,13 +246,10 @@
});
};
if (website.is_editable) {
website.add_template_file('/website/static/src/xml/website.xml');
}
website.add_template_file('/website/static/src/xml/website.xml');
website.dom_ready = $.Deferred();
$(document).ready(function () {
website.is_editable_button= website.is_editable_button || $('html').data('editable');
website.dom_ready.resolve();
// fix for ie
if($.fn.placeholder) $('input, textarea').placeholder();
@ -269,7 +265,11 @@
all_ready = website.dom_ready.then(function () {
return templates_def;
}).then(function () {
if (website.is_editable) {
// display button if they are at least one editable zone in the page (check the branding)
var editable = $('html').data('website-id') && !!$('[data-oe-model]').size();
$("#oe_editzone").toggle(editable);
if ($('html').data('website-id')) {
website.id = $('html').data('website-id');
website.session = new openerp.Session();
var modules = ['website'];
@ -308,6 +308,14 @@
window.document.body.scrollTop = +location.hash.match(/scrollTop=([0-9]+)/)[1];
}
},0);
/* ----- WEBSITE TOP BAR ---- */
var $collapse = $('#oe_applications ul.dropdown-menu').clone()
.attr("id", "oe_applications_collapse")
.attr("class", "nav navbar-nav navbar-left navbar-collapse collapse");
$('#oe_applications').before($collapse);
$collapse.wrap('<div class="visible-xs"/>');
$('[data-target="#oe_applications"]').attr("data-target", "#oe_applications_collapse");
});
return website;

View File

@ -2,30 +2,30 @@
'use strict';
var website = openerp.website;
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=show-mobile-preview]': 'mobilePreview',
}),
mobilePreview: function () {
(new website.MobilePreview()).appendTo($(document.body));
},
});
website.add_template_file('/website/static/src/xml/website.xml');
website.MobilePreview = openerp.Widget.extend({
template: 'website.mobile_preview',
events: {
'hidden.bs.modal': 'destroy'
},
start: function () {
start: function() {
if (!window.location.origin) { // fix for ie9
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
}
document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + window.location.search + "#mobile-preview";
this.$el.modal();
},
destroy: function () {
destroy: function() {
$('.modal-backdrop').remove();
this._super();
},
});
website.ready().done(function() {
$(document.body).on('click', 'a[data-action=show-mobile-preview]', function() {
new website.MobilePreview().appendTo($(document.body));
});
});
})();

View File

@ -4,15 +4,6 @@
var website = openerp.website;
website.add_template_file('/website/static/src/xml/website.seo.xml');
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=promote-current-page]': 'launchSeo',
}),
launchSeo: function () {
(new website.seo.Configurator(this)).appendTo($(document.body));
},
});
website.seo = {};
function analyzeKeyword(htmlPage, keyword) {
@ -524,4 +515,11 @@
this._super();
},
});
website.ready().done(function() {
$(document.body).on('click', 'a[data-action=promote-current-page]', function() {
new website.seo.Configurator(this).appendTo($(document.body));
});
});
})();

View File

@ -84,7 +84,7 @@
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
waitFor: 'button[data-action=save]:not(:visible)',
title: _t("Good Job!"),
content: _t("Well done, you created your homepage."),
popover: { next: _t("Continue") },

View File

@ -12,8 +12,8 @@ window.openerp.website.EditorBar.include({
}
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
$menuItem.click(function () {
T.reset();
T.run(tour.id);
openerp.Tour.reset();
openerp.Tour.run(tour.id);
});
menu.append($menuItem);
});
@ -21,4 +21,4 @@ window.openerp.website.EditorBar.include({
}
});
}());
})();

View File

@ -12,21 +12,20 @@
website.EditorBar.include({
do_not_translate : ['-','*','!'],
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=edit_master]': 'edit_master',
}),
start: function () {
var self = this;
this.initial_content = {};
return this._super.apply(this, arguments).then(function () {
self.$("button[data-action=edit]").removeClass("hidden");
self.$('button[data-action=edit]')
$("button[data-action=edit]").removeClass("hidden");
$('button[data-action=edit]')
.text("Translate");
if (website.is_editable_button) {
self.$('button[data-action=edit]')
.after(openerp.qweb.render('website.TranslatorAdditionalButtons'));
}
self.$('.js_hide_on_translate').hide();
$('button[data-action=edit]')
.parent()
.after(openerp.qweb.render('website.TranslatorAdditionalButtons'));
$('a[data-action=edit_master]').on('click', self, function(ev) {
self.edit_master(ev);
});
$('.js_hide_on_translate').hide();
});
},
edit: function () {

View File

@ -1,5 +1,5 @@
<templates id="template" xml:space="preserve">
<t t-name="website.menu.dialog.submenu">
<t t-name="website.contentMenu.dialog.submenu">
<li t-att-data-menu-id="submenu.id">
<div class="input-group">
<span class="input-group-addon fa fa-bars"/>
@ -17,17 +17,17 @@
<t t-set="children" t-value="submenu.children"/>
<ul t-if="children">
<t t-foreach="children" t-as="submenu">
<t t-call="website.menu.dialog.submenu"/>
<t t-call="website.contentMenu.dialog.submenu"/>
</t>
</ul>
</li>
</t>
<t t-name="website.menu.dialog.edit">
<t t-name="website.contentMenu.dialog.edit">
<t t-call="website.editor.dialog">
<t t-set="title">Edit Menu</t>
<ul class="oe_menu_editor list-unstyled">
<t t-foreach="widget.menu.children" t-as="submenu">
<t t-call="website.menu.dialog.submenu"/>
<t t-call="website.contentMenu.dialog.submenu"/>
</t>
</ul>
<div class="mt16">
@ -42,7 +42,7 @@
</div>
</t>
</t>
<t t-name="website.menu.dialog.add" t-extend="website.editor.dialog.link">
<t t-name="website.contentMenu.dialog.add" t-extend="website.editor.dialog.link">
<t t-jquery="t[t-set='title']" t-operation="inner">
<t t-if="!widget.update_mode">Add Menu Entry</t>
<t t-if="widget.update_mode">Edit Menu Entry</t>

View File

@ -1,18 +1,19 @@
<templates id="template" xml:space="preserve">
<t t-extend="website.editorbar">
<t t-jquery="#website-top-view" t-operation="after">
<div class="navbar-inner" id="website-top-edit">
<form class="navbar-form pull-left text-muted">
<button type="button" data-action="save"
class="btn btn-primary">Save</button>
or
<a href="#" data-action="cancel" class="btn btn-link">Discard</a>
</form>
<ul class="nav navbar-nav pull-right">
<t t-name="website.editorbar">
<div id="website-top-navbar" class="navbar navbar-inverse navbar-fixed-top hidden-xs" style="display: none;">
<div class="navbar-header" id="website-top-edit">
<ul class="nav navbar-nav js_editor_placeholder pull-right">
</ul>
<form class="navbar-form text-muted">
<button type="button" data-action="save"
class="btn btn-primary btn-xs">Save</button>
or
<a href="#" data-action="cancel" class="btn btn-link btn-xs">Discard</a>
</form>
</div>
</t>
</div>
</t>
<t t-name="website.editor.dialog">
<div class="modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.editorbar.menu.promote">
<li><a data-action="promote-current-page" href="#"><span title="Promote page on the web">Promote</span></a></li>
</t>
<t t-name="website.seo_configuration">
<div class="modal fade oe_seo_configuration js_seo_configuration" tabindex="-1" role="dialog">
<div class="modal-dialog">

View File

@ -3,7 +3,9 @@
<!-- Snippet loader -->
<t t-name="website.snippets_button">
<li class="navbar-form js_hide_on_translate"><button type="button" data-action="snippet" class="hidden btn btn-primary">Insert Blocks</button></li>
<li class="navbar-form js_hide_on_translate" style="margin-top: 6px;">
<button type="button" data-action="snippet" class="hidden btn btn-primary btn-xs">Insert Blocks</button>
</li>
</t>
<t t-name="website.snippets_style">
<li class="navbar-form">

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.TranslatorAdditionalButtons">
<span class="oe_translate_or">or</span>
<a class="btn btn-link" data-action="edit_master" href="#">Edit Master</a>
<li><a class="btn btn-link" data-action="edit_master" href="#"> or Edit Master</a></li>
</t>
<t t-name="website.TranslatorDialog">
<div class="modal fade oe_website_translator" tabindex="-1" role="dialog">

View File

@ -2,53 +2,6 @@
<!-- vim:fdl=1:
-->
<templates id="template" xml:space="preserve">
<t t-name="website.editorbar">
<div id="website-top-navbar" class="navbar navbar-inverse navbar-fixed-top">
<div id="website-top-view">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-edit-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<form class="navbar-form navbar-left">
<button type="button" data-action="edit"
class="btn btn-primary hidden">Edit</button>
</form>
</div>
<div class="collapse navbar-collapse navbar-edit-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a data-action="show-mobile-preview" href="#"><span title="Mobile preview"><i class="fa fa-mobile"/></span></a></li>
<li class="divider-vertical"></li>
<li><a data-action="promote-current-page" href="#"><span title="Promote page on the web">Promote</span></a></li>
<li class="hidden dropdown js_hide_on_translate">
<a id="content-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">Content <span class="caret"></span></a>
<ul class="dropdown-menu oe_content_menu" role="menu">
<li class="hidden"><a data-action="edit-structure" href="#"><span title="Edit Top Menu">Edit Menu</span></a></li>
<li class="hidden divider"> </li>
<li class="hidden"><a href="#" data-action="new_page">New Page</a></li>
</ul>
</li>
<li class="dropdown js_hide_on_translate">
<a id="customize-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">Customize <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" id="customize-menu">
<!-- filled in JS -->
</ul>
</li>
<li class="dropdown js_hide_on_translate">
<a id="help-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">Help <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" id="help-menu">
<!-- filled in JS -->
</ul>
</li>
</ul>
</div>
</div>
</div>
</t>
<t t-name="website.mobile_preview">
<div class="modal fade oe_mobile_preview" tabindex="-1" role="dialog">
<div class="modal-dialog">
@ -124,5 +77,4 @@
</div>
</div>
</t>
</templates>

View File

@ -1,5 +1,6 @@
import openerp.tests
class TestUi(openerp.tests.HttpCase):
def test_01_public_homepage(self):
self.phantom_js("/", "console.log('ok')", "openerp.website.snippet")

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Front-end/Back-end integration -->
<template id="user_navbar" inherit_id="website.layout" groups="base.group_user">
<xpath expr="//body/div['id=wrawrap']" position="before">
<nav id="oe_main_menu_navbar" class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#oe_applications">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<ul id="oe_applications" class="nav navbar-nav navbar-left navbar-collapse collapse">
<li class="dropdown active">
<a href="#" class="dropdown-toggle hidden-xs" data-toggle="dropdown"><i class="fa fa-th-large"></i> Website<b class="caret"/></a>
<ul class="dropdown-menu">
<li t-foreach="menu_data['children']" t-as="menu">
<t t-call="web.menu_link"/>
</li>
</ul>
</li>
</ul>
<ul id="oe_editzone" class="nav navbar-nav hidden-xs" style="padding-left: 20px;" groups="base.group_website_publisher">
<li><button type="button" data-action="edit" class="btn btn-primary btn-xs" style="display: none;">Edit</button></li>
</ul>
<ul id="oe_systray" class="nav navbar-nav navbar-right pull-right hidden-xs">
<!-- Mobile -->
<li>
<a data-action="show-mobile-preview" href="#">
<span title="Mobile preview"><i class="fa fa-mobile"/></span>
</a>
</li>
<!-- Promote -->
<li groups="base.group_website_designer">
<a data-action="promote-current-page" href="#">
<span title="Promote page on the web">Promote</span>
</a>
</li>
<!-- Content -->
<li class="dropdown" groups="base.group_website_publisher">
<a id="content-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">
Content <span class="caret"></span>
</a>
<ul class="dropdown-menu oe_content_menu" role="menu">
<li groups="base.group_website_designer"><a data-action="edit_menu" href="#"><span title="Edit Top Menu">Edit Menu</span></a></li>
<li class="divider" groups="base.group_website_designer"> </li>
<li groups="base.group_website_designer"><a href="#" data-action="new_page">New Page</a></li>
</ul>
</li>
<!-- Customize -->
<li class="dropdown" id="customize-menu" groups="base.group_website_designer">
<a id="customize-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">
Customize <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><a href="#advanced-view-editor" data-action='ace'>HTML Editor</a></li>
<li><a href="/page/website.themes">Change Theme</a></li>
<li><a href="/web#return_label=Website&amp;action=website.action_module_website">Install Apps</a></li>
<li class="divider"></li>
</ul>
</li>
<!-- Help -->
<li class="dropdown">
<a id="help-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">
Help <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" id="help-menu">
<li class="dropdown-header">HELP &amp; TUTORIALS</li>
</ul>
</li>
</ul>
</nav>
</xpath>
</template>
</data>
</openerp>

View File

@ -174,36 +174,6 @@
</template>
<template id="editor_head" inherit_id="website.layout" name="Editor" groups="base.group_website_publisher,base.group_website_designer">
<xpath expr="//body" position="attributes">
<attribute name="style">padding-top: 51px;</attribute>
</xpath>
<xpath expr="//body" position="inside">
<div id="website-top-navbar-placeholder" class="navbar navbar-inverse navbar-fixed-top hidden-xs">
<div class="navbar-header">
<form class="navbar-form navbar-left" title="editable_no_editor or None">
<button type="button" class="btn btn-primary"
disabled="'disabled' if editable_no_editor else None"
>Edit</button>
</form>
</div>
<div class="collapse navbar-collapse navbar-edit-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#" onclick="return false;"><i class="fa fa-mobile" title="Mobile preview"/></a></li>
<li class="divider-vertical"/>
<li><a href="#" onclick="return false;"><span title="Promote page on the web">Promote</span></a></li>
<li class="dropdown">
<a href="#" onclick="return false;">Content <span class="caret"/></a>
</li>
<li class="dropdown">
<a href="#" onclick="return false;">Customize <span class="caret"/></a>
</li>
<li class="dropdown">
<a href="#" onclick="return false;">Help <span class="caret"/></a>
</li>
</ul>
</div>
</div>
</xpath>
<xpath expr='//t[@t-call-assets="website.assets_frontend"]' position="after">
<t t-call-assets="website.assets_editor"/>
</xpath>
@ -215,22 +185,21 @@
</script>
<link rel='stylesheet' href='/website/static/src/css/snippets.css'/>
<link rel='stylesheet' href='/website/static/src/css/editor.css'/>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
<link rel="stylesheet" href="/website/static/lib/select2-bootstrap-css/select2-bootstrap.css"/>
<script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
<script type="text/javascript" src="/web/static/lib/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/ace/theme-monokai.js"></script>
<script type="text/javascript" src="/website/static/lib/ace/mode-xml.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/website/static/lib/jQuery.transfo.js"></script>
<t t-call="web.jqueryui_conflict">
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
</t>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
@ -238,8 +207,7 @@
<script type="text/javascript" src="/website/static/lib/MutationObservers/MutationObserver.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.newpage.js"></script> <!-- groups="base.group_website_designer" -->
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script> <!-- groups="base.group_website_designer" -->
<script type="text/javascript" src="/website/static/src/js/website.contentMenu.js"></script> <!-- groups="base.group_website_designer" -->
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
@ -248,7 +216,6 @@
<script type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
<script type="text/javascript" src="/website/static/src/js/jQuery.transfo.js"></script>
</template>
<template id="login_layout" inherit_id="web.login_layout" name="Website Login Layout">

View File

@ -3,31 +3,24 @@
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_blog/static/src/xml/website_blog.xml');
website.EditorBarContent.include({
new_blog_post: function() {
website.prompt({
id: "editor_new_blog",
window_title: _t("New Blog Post"),
select: "Select Blog",
init: function (field) {
return website.session.model('blog.blog')
.call('name_search', [], { context: website.get_context() });
},
}).then(function (cat_id) {
document.location = '/blogpost/new?blog_id=' + cat_id;
});
},
});
website.EditorBar.include({
start: function() {
website.is_editable_button = website.is_editable_button || !!$("#wrap.js_blog").size();
var res = this._super();
this.$(".dropdown:has(.oe_content_menu)").removeClass("hidden");
return res;
},
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=new_blog_post]': function (ev) {
ev.preventDefault();
website.prompt({
id: "editor_new_blog",
window_title: _t("New Blog Post"),
select: "Select Blog",
init: function (field) {
return website.session.model('blog.blog')
.call('name_search', [], { context: website.get_context() });
},
}).then(function (cat_id) {
document.location = '/blogpost/new?blog_id=' + cat_id;
});
},
}),
edit: function () {
var self = this;
$('.popover').remove();

View File

@ -1,7 +0,0 @@
<templates id="template" xml:space="preserve">
<t t-extend="website.editorbar">
<t t-jquery="ul.oe_content_menu" t-operation="append">
<li><a href="#" data-action="new_blog_post">New Blog Post</a></li>
</t>
</t>
</templates>

View File

@ -461,5 +461,11 @@
</xpath>
</template>
<!-- User Navbar -->
<template id="content_new_blogpost" inherit_id="website.user_navbar" groups="base.group_document_user">
<xpath expr="//ul[@id='oe_systray']/li/ul[@class='dropdown-menu oe_content_menu']" position="inside">
<li><a href="#" data-action="new_blog_post">New Blog Post</a></li>
</xpath>
</template>
</data>
</openerp>

View File

@ -3,28 +3,18 @@
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_event/static/src/xml/website_event.xml');
website.EditorBar.include({
start: function() {
website.is_editable_button = website.is_editable_button || !!$("#wrap.js_event").size();
var res = this._super();
this.$(".dropdown:has(.oe_content_menu)").removeClass("hidden");
return res;
},
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=new_event]': function (ev) {
ev.preventDefault();
website.prompt({
id: "editor_new_event",
window_title: _t("New Event"),
input: "Event Name",
}).then(function (event_name) {
website.form('/event/add_event', 'POST', {
event_name: event_name
});
website.EditorBarContent.include({
new_event: function() {
website.prompt({
id: "editor_new_event",
window_title: _t("New Event"),
input: "Event Name",
}).then(function (event_name) {
website.form('/event/add_event', 'POST', {
event_name: event_name
});
}
}),
});
},
});
})();

View File

@ -1,7 +0,0 @@
<templates id="template" xml:space="preserve">
<t t-extend="website.editorbar">
<t t-jquery="ul.oe_content_menu" t-operation="append">
<li><a href="#" data-action="new_event">New Event</a></li>
</t>
</t>
</templates>

View File

@ -429,5 +429,12 @@
</t>
</t>
</template>
<!-- User Navbar -->
<template id="content_new_event" inherit_id="website.user_navbar" groups="event.group_event_manager">
<xpath expr="//ul[@id='oe_systray']/li/ul[@class='dropdown-menu oe_content_menu']" position="inside">
<li><a href="#" data-action="new_event">New Event</a></li>
</xpath>
</template>
</data>
</openerp>

View File

@ -3,28 +3,18 @@
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_forum/static/src/xml/website_forum.xml');
website.EditorBar.include({
start: function() {
website.is_editable_button = website.is_editable_button || !!$("#wrap").size();
var res = this._super();
this.$(".dropdown:has(.oe_content_menu)").removeClass("hidden");
return res;
},
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=new_forum]': function (ev) {
ev.preventDefault();
website.prompt({
id: "editor_new_forum",
window_title: _t("New Forum"),
input: "Forum Name",
}).then(function (forum_name) {
website.form('/forum/new', 'POST', {
forum_name: forum_name
});
website.EditorBarContent.include({
new_forum: function() {
website.prompt({
id: "editor_new_forum",
window_title: _t("New Forum"),
input: "Forum Name",
}).then(function (forum_name) {
website.form('/forum/new', 'POST', {
forum_name: forum_name
});
}
}),
});
},
});
})();

View File

@ -1,7 +0,0 @@
<templates id="template" xml:space="preserve">
<t t-extend="website.editorbar">
<t t-jquery="ul.oe_content_menu" t-operation="append">
<li><a href="#" data-action="new_forum">New Forum</a></li>
</t>
</t>
</templates>

View File

@ -1019,5 +1019,12 @@
</div>
</template>
<!-- User Navbar -->
<template id="content_new_forum" inherit_id="website.user_navbar" groups="base.group_user">
<xpath expr="//ul[@id='oe_systray']/li/ul[@class='dropdown-menu oe_content_menu']" position="inside">
<li><a href="#" data-action="new_forum">New Forum</a></li>
</xpath>
</template>
</data>
</openerp>

View File

@ -59,7 +59,7 @@ class website_hr_recruitment(http.Controller):
'countries': countries
})
@http.route('/jobs/add', type='http', auth="user", methods=['POST'], website=True)
@http.route('/jobs/add', type='http', auth="user", website=True)
def jobs_add(self, **kwargs):
cr, uid, context = request.cr, request.uid, request.context
value = {

View File

@ -1,14 +0,0 @@
(function() {
"use strict";
var website = openerp.website;
website.add_template_file('/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml');
website.EditorBar.include({
start: function() {
website.is_editable_button = website.is_editable_button || !!$("#wrap.js_hr_recruitment").size();
var res = this._super();
this.$(".dropdown:has(.oe_content_menu)").removeClass("hidden");
return res;
},
});
})();

View File

@ -1,7 +0,0 @@
<templates id="template" xml:space="preserve">
<t t-extend="website.editorbar">
<t t-jquery="ul.oe_content_menu" t-operation="append">
<li><a href="/jobs/add" class="js_link2post">New Job Offer</a></li>
</t>
</t>
</templates>

View File

@ -2,12 +2,6 @@
<openerp>
<data>
<template id="assets_editor" inherit_id="website.assets_editor" name="Jobs Editor" groups="base.group_hr_manager">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_hr_recruitment/static/src/js/website_hr_recruitment.editor.js"></script>
</xpath>
</template>
<template id="job_footer_custom" inherit_id="website.footer_default" name="Footer Job Link">
<xpath expr="//div[@id='info']/ul" position="inside">
<li><a href="/jobs">Jobs</a></li>
@ -291,5 +285,12 @@
</xpath>
</template>
<!-- User Navbar -->
<template id="content_new_job_offer" inherit_id="website.user_navbar" groups="base.group_hr_manager">
<xpath expr="//ul[@id='oe_systray']/li/ul[@class='dropdown-menu oe_content_menu']" position="inside">
<li><a href="/jobs/add" class="js_link2post">New Job Offer</a></li>
</xpath>
</template>
</data>
</openerp>

View File

@ -58,8 +58,9 @@
<script type="text/javascript" src="/website/static/lib/jquery.placeholder/jquery.placeholder.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.newpage.js"></script> <!-- groups="base.group_website_designer" -->
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script> <!-- groups="base.group_website_designer" -->
<script type="text/javascript" src="/website/static/src/js/website.contentMenu.js"></script> <!-- groups="base.group_website_designer" -->
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.snippets.editor.js"></script>

View File

@ -96,7 +96,7 @@
popover: { fixed: true },
},
{
waitFor: '#website-top-navbar button[data-action="edit"]:visible',
waitFor: '#website-top-navbar:hidden',
element: '.js_publish_management button.js_publish_btn.btn-danger',
placement: 'top',
title: _t("Publish your product"),

View File

@ -3,29 +3,19 @@
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_sale/static/src/xml/website_sale.xml');
website.EditorBar.include({
start: function() {
website.is_editable_button = website.is_editable_button || !!$("#wrap.js_sale").size();
var res = this._super();
this.$(".dropdown:has(.oe_content_menu)").removeClass("hidden");
return res;
},
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=new_product]': function (ev) {
ev.preventDefault();
website.prompt({
id: "editor_new_product",
window_title: _t("New Product"),
input: "Product Name",
}).then(function (name) {
website.form('/shop/add_product', 'POST', {
name: name
});
website.EditorBarContent.include({
new_product: function() {
website.prompt({
id: "editor_new_product",
window_title: _t("New Product"),
input: "Product Name",
}).then(function (name) {
website.form('/shop/add_product', 'POST', {
name: name
});
}
}),
});
},
});
function reload_enable_editor() {

View File

@ -1,7 +0,0 @@
<templates id="template" xml:space="preserve">
<t t-extend="website.editorbar">
<t t-jquery="ul.oe_content_menu" t-operation="append">
<li><a id="create-new-product" href="#" data-action="new_product">New Product</a></li>
</t>
</t>
</templates>

View File

@ -1187,5 +1187,12 @@
</table>
</template>
<!-- User Navbar -->
<template id="content_new_product" inherit_id="website.user_navbar" groups="base.group_sale_manager">
<xpath expr="//ul[@id='oe_systray']/li/ul[@class='dropdown-menu oe_content_menu']" position="inside">
<li><a id="create-new-product" href="#" data-action="new_product">New Product</a></li>
</xpath>
</template>
</data>
</openerp>

View File

@ -23,6 +23,7 @@
import base64
import re
import threading
import operator
from openerp.tools.safe_eval import safe_eval as eval
from openerp import tools
import openerp.modules
@ -32,6 +33,7 @@ from openerp import SUPERUSER_ID
MENU_ITEM_SEPARATOR = "/"
class ir_ui_menu(osv.osv):
_name = 'ir.ui.menu'
@ -338,6 +340,63 @@ class ir_ui_menu(osv.osv):
res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, dom, context=context)
return res
def get_user_roots(self, cr, uid, context=None):
""" Return all root menu ids visible for the user.
:return: the root menu ids
:rtype: list(int)
"""
menu_domain = [('parent_id', '=', False)]
return self.search(cr, uid, menu_domain, context=context)
def load_menus(self, cr, uid, context=None):
""" Loads all menu items (all applications and their sub-menus).
:return: the menu root
:rtype: dict('children': menu_nodes)
"""
fields = ['name', 'sequence', 'parent_id', 'action']
menu_root_ids = self.get_user_roots(cr, uid, context=context)
menu_roots = self.read(cr, uid, menu_root_ids, fields, context=context) if menu_root_ids else []
menu_root = {
'id': False,
'name': 'root',
'parent_id': [-1, ''],
'children': menu_roots,
'all_menu_ids': menu_root_ids,
}
if not menu_roots:
return menu_root
# menus are loaded fully unlike a regular tree view, cause there are a
# limited number of items (752 when all 6.1 addons are installed)
menu_ids = self.search(cr, uid, [('id', 'child_of', menu_root_ids)], 0, False, False, context=context)
menu_items = self.read(cr, uid, menu_ids, fields, context=context)
# adds roots at the end of the sequence, so that they will overwrite
# equivalent menu items from full menu read when put into id:item
# mapping, resulting in children being correctly set on the roots.
menu_items.extend(menu_roots)
menu_root['all_menu_ids'] = menu_ids # includes menu_root_ids!
# make a tree using parent_id
menu_items_map = dict(
(menu_item["id"], menu_item) for menu_item in menu_items)
for menu_item in menu_items:
if menu_item['parent_id']:
parent = menu_item['parent_id'][0]
else:
parent = False
if parent in menu_items_map:
menu_items_map[parent].setdefault(
'children', []).append(menu_item)
# sort by sequence a tree using parent_id
for menu_item in menu_items:
menu_item.setdefault('children', []).sort(
key=operator.itemgetter('sequence'))
return menu_root
_columns = {
'name': fields.char('Menu', required=True, translate=True),
'sequence': fields.integer('Sequence'),