mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Menu and application controller refactoring
https://fedorahosted.org/freeipa/ticket/3235 https://fedorahosted.org/freeipa/ticket/3236
This commit is contained in:
@@ -28,43 +28,6 @@
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
|
||||
<div id="background">
|
||||
<div id="background-header"></div>
|
||||
<div id="background-navigation"></div>
|
||||
<div id="background-left"></div>
|
||||
<div id="background-center"></div>
|
||||
<div id="background-right"></div>
|
||||
</div>
|
||||
|
||||
<div id="header">
|
||||
<span class="header-logo">
|
||||
<a href="#"><img src="images/ipa-logo.png" /><img src="images/ipa-banner.png" /></a>
|
||||
</span>
|
||||
<span class="header-right">
|
||||
<span class="header-passwordexpires"></span>
|
||||
<span id="loggedinas" class="header-loggedinas" style="visibility:hidden;">
|
||||
<a href="#"><span id="login_header">Logged in as</span>: <span class="login"></span></a>
|
||||
</span>
|
||||
<span class="header-loggedinas" style="visibility:hidden;">
|
||||
| <a href="#logout" id="logout">Logout</a>
|
||||
</span>
|
||||
<span id="header-network-activity-indicator" class="network-activity-indicator">
|
||||
<img src="images/spinner-header.gif" />
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="navigation"></div>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<body></body>
|
||||
|
||||
</html>
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ body {
|
||||
}
|
||||
|
||||
/* ---- Navigation ---- */
|
||||
#navigation {
|
||||
.navigation {
|
||||
position: absolute;
|
||||
top: 34px;
|
||||
left: 6px;
|
||||
@@ -288,64 +288,61 @@ body {
|
||||
height: 102px;
|
||||
}
|
||||
|
||||
#navigation.tabs-3 {
|
||||
.navigation ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.navigation .submenu li {
|
||||
float: left;
|
||||
position: relative;
|
||||
list-style: none;
|
||||
white-space:nowrap;
|
||||
}
|
||||
/*
|
||||
.navigation.tabs-3 {
|
||||
height: 150px;
|
||||
}
|
||||
}*/
|
||||
|
||||
div.tabs {
|
||||
.submenu {
|
||||
width: 100%;
|
||||
min-height: 4em;
|
||||
background: transparent;
|
||||
/* min-height: 4em;
|
||||
background: transparent;*/
|
||||
}
|
||||
|
||||
.tabs.ui-tabs, .tabs .ui-tabs {
|
||||
padding: 0;
|
||||
}
|
||||
/* ---- Navigation level 1 ---- */
|
||||
|
||||
/* ---- Tabs level 1 ---- */
|
||||
|
||||
.tabs.ui-widget {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav > .ui-state-hover {
|
||||
background: url(images/hover-tab.png);
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav {
|
||||
padding: 33px 0 0;
|
||||
.menu-level-1 > ul {
|
||||
height: 38px;
|
||||
padding: 34px 0 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
/* border: none;*/
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav li {
|
||||
-moz-border-radius: 0 !important;
|
||||
-webkit-border-radius: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
.menu-level-1 > ul > li {
|
||||
height: 36px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid #A0A0A0;
|
||||
background: none;
|
||||
border-bottom:none;
|
||||
background-image: url(images/mainnav-tab-off.png);
|
||||
margin: 0 0.4em 0 0;
|
||||
text-align: center;
|
||||
vertical-align:baseline;
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav > li.ui-tabs-selected {
|
||||
padding: 0;
|
||||
background-image: url(images/mainnav-tab-on.png);
|
||||
text-align: center;
|
||||
.menu-level-1 > ul > li.ui-state-hover,
|
||||
.menu-level-1 > ul > li:hover {
|
||||
background: url(images/hover-tab.png);
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav > li > a {
|
||||
-moz-border-radius: 0 !important;
|
||||
-webkit-border-radius: 0 !important;
|
||||
.menu-level-1 > ul > li.selected {
|
||||
padding-bottom: 1px;
|
||||
background-image: url(images/mainnav-tab-on.png);
|
||||
}
|
||||
|
||||
.menu-level-1 > ul > li > a {
|
||||
font-family: "Overpass Bold","Liberation Sans", Arial, sans-serif;
|
||||
min-width: 5em;
|
||||
height: 20px;
|
||||
line-height: 38px;
|
||||
color: #858585;
|
||||
margin: 0 auto;
|
||||
text-align:center;
|
||||
@@ -353,54 +350,41 @@ div.tabs {
|
||||
text-shadow: 1px 1px 0 #FFFFFF;
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav > li > a:link,
|
||||
span.main-nav-off > a:visited{
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
.tabs1 > .ui-tabs-nav > li.ui-tabs-selected > a {
|
||||
.menu-level-1 > ul > li.selected > a {
|
||||
color: #1e5e05;
|
||||
}
|
||||
|
||||
.tabs1 .ui-tabs-panel {
|
||||
|
||||
/* ---- Navigation level 2 ---- */
|
||||
|
||||
.menu-level-2 {
|
||||
display: block;
|
||||
border-width: 0;
|
||||
padding: 0 0 0 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ---- Tabs level 2 ---- */
|
||||
|
||||
.tabs2 {
|
||||
}
|
||||
|
||||
.tabs2 > .ui-tabs-nav {
|
||||
.menu-level-2 > ul {
|
||||
padding: 5px 24px 1px;
|
||||
margin: 0;
|
||||
height: 25px;
|
||||
border: none;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tabs2 > .ui-tabs-nav > li {
|
||||
.menu-level-2 > ul > li {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
background: none repeat scroll 0 0 transparent !important;
|
||||
color: white;
|
||||
border: none;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.tabs2 > .ui-tabs-nav > li.ui-tabs-selected {
|
||||
.menu-level-2 > ul > li.selected {
|
||||
background: url(images/nav-arrow.png) no-repeat scroll center 2.1em transparent !important;
|
||||
height: 3.1em;
|
||||
height: 31px;
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs2 > .ui-tabs-nav > li > a {
|
||||
.menu-level-2 > ul > li > a {
|
||||
width:auto;
|
||||
padding: 0.3em 0.8em ;
|
||||
-moz-border-radius: 2em !important;
|
||||
@@ -412,38 +396,28 @@ span.main-nav-off > a:visited{
|
||||
margin: 0 0.3em;
|
||||
}
|
||||
|
||||
.tabs2 > .ui-tabs-nav li > a:link,
|
||||
span.main-nav-off > a:visited {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.tabs2 > .ui-tabs-nav > li.ui-tabs-selected > a,
|
||||
.tabs2 > .ui-tabs-nav > li > a:hover {
|
||||
.menu-level-2 > ul > li.selected > a,
|
||||
.menu-level-2 > ul > li > a:hover {
|
||||
background-color:#EEEEEE;
|
||||
color: #164304;
|
||||
text-shadow: 1px 1px 0 #FFFFFF;
|
||||
|
||||
}
|
||||
|
||||
/* ---- Tabs level 3 ---- */
|
||||
|
||||
.tabs3 {
|
||||
/* ---- Navigation level 3 ---- */
|
||||
.menu-level-3 {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.tabs3 > .ui-tabs-nav {
|
||||
padding: 1em 22px 0.1em;
|
||||
border: none;
|
||||
background: transparent;
|
||||
.menu-level-3 > ul {
|
||||
padding: 0 22px 0.1em;
|
||||
}
|
||||
|
||||
.tabs3 > .ui-tabs-nav > li {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
.menu-level-3 > ul > li {
|
||||
margin: 0 2.4em 1px 0;
|
||||
}
|
||||
|
||||
.tabs3 > .ui-tabs-nav > li > a {
|
||||
.menu-level-3 > ul > li > a {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
padding: 0.3em 0 0.3em 0;
|
||||
@@ -453,7 +427,7 @@ span.main-nav-off > a:visited {
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
.tabs3 > .ui-tabs-nav > li.ui-tabs-selected > a {
|
||||
.menu-level-3 > ul > li.selected > a {
|
||||
font-family: "Overpass Bold", "Liberation Sans", Arial, sans-serif;
|
||||
color: #1e5e05;
|
||||
}
|
||||
@@ -467,7 +441,7 @@ span.main-nav-off > a:visited {
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#content.tabs-3 {
|
||||
#content.nav-space-3 {
|
||||
top: 175px;
|
||||
}
|
||||
|
||||
|
||||
@@ -132,5 +132,7 @@
|
||||
+process src/libs/jquery.ordered-map.js
|
||||
+process src/freeipa/*.js
|
||||
+process src/freeipa/_base/*.js
|
||||
+process src/freeipa/navigation/*.js
|
||||
+process src/freeipa/widgets/*.js
|
||||
+process src/*.js
|
||||
+process ./*.js
|
||||
323
install/ui/src/freeipa/Application_controller.js
Normal file
323
install/ui/src/freeipa/Application_controller.js
Normal file
@@ -0,0 +1,323 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2012 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Application controller
|
||||
*
|
||||
* Controls interaction between navigation, menu and facets.
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'dojo/_base/lang',
|
||||
'dojo/_base/array',
|
||||
'dojo/on',
|
||||
'dojo/topic',
|
||||
'dojo/query',
|
||||
'dojo/dom-class',
|
||||
'./widgets/App',
|
||||
'./ipa',
|
||||
'./navigation/Menu',
|
||||
'./navigation/Router',
|
||||
'./navigation/menu_spec'
|
||||
],
|
||||
function(declare, lang, array, on, topic, query, dom_class,
|
||||
App_widget, IPA, Menu, Router, menu_spec) {
|
||||
|
||||
/**
|
||||
* Main application
|
||||
*
|
||||
* This class serves as top level widget. It creates basic UI: controls
|
||||
* rendering of header, footer and placeholder for facets.
|
||||
*/
|
||||
var App = declare(null, {
|
||||
|
||||
app_widget: null,
|
||||
|
||||
router: null,
|
||||
|
||||
menu: null,
|
||||
|
||||
initialized: false,
|
||||
|
||||
facet_changing: false,
|
||||
|
||||
init: function() {
|
||||
this.menu = new Menu();
|
||||
this.router = new Router();
|
||||
this.app_widget = new App_widget();
|
||||
this.app_widget.menu_widget.set_menu(this.menu);
|
||||
this.app_widget.container_node = query('body')[0];
|
||||
|
||||
on(this.app_widget.menu_widget, 'item-select', lang.hitch(this, this.on_menu_click));
|
||||
on(this.app_widget, 'profile-click', lang.hitch(this, this.on_profile));
|
||||
on(this.app_widget, 'logout-click', lang.hitch(this, this.on_logout));
|
||||
on(this.menu, 'selected', lang.hitch(this, this.on_menu_select));
|
||||
|
||||
topic.subscribe('facet-show', lang.hitch(this, this.on_facet_show));
|
||||
topic.subscribe('facet-change', lang.hitch(this, this.on_facet_change));
|
||||
topic.subscribe('facet-change-canceled', lang.hitch(this, this.on_facet_canceled));
|
||||
topic.subscribe('phase-error', lang.hitch(this, this.on_phase_error));
|
||||
topic.subscribe('facet-state-change', lang.hitch(this, this.on_facet_state_changed));
|
||||
|
||||
this.app_widget.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets:
|
||||
* * metadata
|
||||
* * server configuration
|
||||
* * user information
|
||||
*/
|
||||
get_configuration: function(success_handler, error_handler) {
|
||||
IPA.init({ on_success: success_handler, on_error: error_handler});
|
||||
},
|
||||
|
||||
/**
|
||||
* Deduces current application profile - administraion or self-service.
|
||||
* Initializes profiles's menu.
|
||||
*/
|
||||
choose_profile: function() {
|
||||
|
||||
// TODO: change IPA.whoami.cn[0] to something readable
|
||||
this.update_logged_in(true, IPA.whoami.cn[0]);
|
||||
var selfservice = this.is_selfservice();
|
||||
|
||||
|
||||
this.app_widget.menu_widget.ignore_changes = true;
|
||||
|
||||
if (selfservice) {
|
||||
this.menu.name = menu_spec.self_service.name;
|
||||
this.menu.add_items(menu_spec.self_service.items);
|
||||
} else {
|
||||
this.menu.name = menu_spec.admin.name;
|
||||
this.menu.add_items(menu_spec.admin.items);
|
||||
}
|
||||
|
||||
this.app_widget.menu_widget.ignore_changes = false;
|
||||
this.app_widget.menu_widget.render();
|
||||
this.app_widget.menu_widget.select(this.menu.selected);
|
||||
|
||||
// now we are ready for displaying a facet
|
||||
// cat match a facet if hash is set
|
||||
this.router.startup();
|
||||
|
||||
// choose default facet if not defined by route
|
||||
if (!this.current_facet) {
|
||||
if (selfservice) {
|
||||
this.on_profile();
|
||||
} else {
|
||||
this.router.navigate_to_entity_facet('user', 'search');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
is_selfservice: function() {
|
||||
var whoami = IPA.whoami;
|
||||
var self_service = true;
|
||||
|
||||
|
||||
if (whoami.hasOwnProperty('memberof_group') &&
|
||||
whoami.memberof_group.indexOf('admins') !== -1) {
|
||||
self_service = false;
|
||||
} else if (whoami.hasOwnProperty('memberofindirect_group')&&
|
||||
whoami.memberofindirect_group.indexOf('admins') !== -1) {
|
||||
self_service = false;
|
||||
} else if (whoami.hasOwnProperty('memberof_role') &&
|
||||
whoami.memberof_role.length > 0) {
|
||||
self_service = false;
|
||||
} else if (whoami.hasOwnProperty('memberofindirect_role') &&
|
||||
whoami.memberofindirect_role.length > 0) {
|
||||
self_service = false;
|
||||
}
|
||||
|
||||
IPA.is_selfservice = self_service; // quite ugly, needed for users
|
||||
|
||||
return self_service;
|
||||
},
|
||||
|
||||
update_logged_in: function(logged_in, fullname) {
|
||||
this.app_widget.set('logged', logged_in);
|
||||
this.app_widget.set('fullname', fullname);
|
||||
},
|
||||
|
||||
on_profile: function() {
|
||||
this.router.navigate_to_entity_facet('user', 'details', [IPA.whoami.uid[0]]);
|
||||
},
|
||||
|
||||
on_logout: function(event) {
|
||||
IPA.logout();
|
||||
},
|
||||
|
||||
on_phase_error: function(error) {
|
||||
// FIXME: CHANGE!!!
|
||||
window.alert('Initialization error, have a coffee and relax.');
|
||||
// var container = $('#content').empty();
|
||||
// container.append('<p>Error: '+error_thrown.name+'</p>');
|
||||
// container.append('<p>'+error_thrown.message+'</p>');
|
||||
},
|
||||
|
||||
on_facet_change: function(event) {
|
||||
//this.facet_changing = true;
|
||||
var new_facet = event.facet;
|
||||
var current_facet = this.current_facet;
|
||||
|
||||
if (current_facet && !current_facet.can_leave()) {
|
||||
var permit_clb = function() {
|
||||
// Some facet's might not call reset before this call but after
|
||||
// so they are still dirty. Calling reset prevent's opening of
|
||||
// dirty dialog again.
|
||||
if (current_facet.is_dirty()) current_facet.reset(); //TODO change
|
||||
this.router.navigate_to_hash(event.hash, event.facet);
|
||||
};
|
||||
|
||||
var dialog = current_facet.show_leave_dialog(permit_clb);
|
||||
this.router.canceled = true;
|
||||
dialog.open();
|
||||
}
|
||||
},
|
||||
|
||||
on_facet_canceled: function(event) {
|
||||
},
|
||||
|
||||
on_facet_state_changed: function(event) {
|
||||
if (event.facet === this.current_facet) {
|
||||
var hash = this.router.create_hash(event.facet, event.state);
|
||||
this.router.update_hash(hash, true);
|
||||
}
|
||||
},
|
||||
|
||||
on_facet_show: function(event) {
|
||||
var facet = event.facet;
|
||||
|
||||
// update menu
|
||||
var menu_item = this._find_menu_item(facet);
|
||||
if (menu_item) this.menu.select(menu_item);
|
||||
|
||||
if (!facet.container) {
|
||||
facet.container_node = this.app_widget.content_node;
|
||||
}
|
||||
if (this.current_facet) {
|
||||
this.current_facet.hide();
|
||||
}
|
||||
this.current_facet = facet;
|
||||
facet.show();
|
||||
},
|
||||
|
||||
_find_menu_item: function(facet) {
|
||||
|
||||
var items;
|
||||
|
||||
// entity facets
|
||||
if (facet.entity) {
|
||||
items = this.menu.query({ entity: facet.entity.name, facet: facet.name });
|
||||
}
|
||||
|
||||
// normal facets
|
||||
if (!items.total) {
|
||||
items = this.menu.query({ facet: facet.name });
|
||||
}
|
||||
|
||||
// entity fallback
|
||||
if (!items.total && facet.entity) {
|
||||
items = this.menu.query({ entity: facet.entity.name });
|
||||
}
|
||||
|
||||
// fallback: Top level item
|
||||
if (!items.total) {
|
||||
items = this.menu.query({ parent: null });
|
||||
}
|
||||
|
||||
// select first
|
||||
if (items.total) {
|
||||
return items[0];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to find menu item with assigned facet and navigate to it.
|
||||
*/
|
||||
on_menu_click: function(menu_item) {
|
||||
this._navigate_to_menu_item(menu_item);
|
||||
},
|
||||
|
||||
_navigate_to_menu_item: function(menu_item) {
|
||||
|
||||
if (menu_item.entity) {
|
||||
// entity pages
|
||||
this.router.navigate_to_entity_facet(
|
||||
menu_item.entity,
|
||||
menu_item.facet,
|
||||
menu_item.pkeys,
|
||||
menu_item.args);
|
||||
} else if (menu_item.facet) {
|
||||
// concrete facets
|
||||
this.router.navigate_to_facet(menu_item.facet, menu_item.args);
|
||||
} else {
|
||||
// categories, select first posible child
|
||||
var children = this.menu.query({parent: menu_item.name });
|
||||
if (children.total) {
|
||||
var success = false;
|
||||
for (var i=0; i<children.total;i++) {
|
||||
success = this._navigate_to_menu_item(children[i]);
|
||||
if (success) break;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Watches menu changes and adjusts facet space when there is
|
||||
* a need for larger menu space.
|
||||
*
|
||||
* Show extended menu space when:
|
||||
* * there is 3+ levels of menu
|
||||
*
|
||||
* Don't show when:
|
||||
* * all items of levels 3+ are hidden
|
||||
*/
|
||||
on_menu_select: function(select_state) {
|
||||
|
||||
var has_visible = function(query_result) {
|
||||
for (var i=0; i<query_result.total; i++) {
|
||||
if (!query_result[i].hidden) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var item = select_state.item;
|
||||
var visible_simblings = has_visible(this.menu.query({parent: item.parent}));
|
||||
var visible_children = has_visible(this.menu.query({parent: item.name}));
|
||||
|
||||
var levels = select_state.new_selection.length;
|
||||
|
||||
var three_levels = levels >= 3 && (visible_children > 0 || visible_simblings > 0);
|
||||
|
||||
dom_class.toggle(this.app_widget.content_node,
|
||||
'nav-space-3',
|
||||
three_levels);
|
||||
}
|
||||
});
|
||||
|
||||
return App;
|
||||
});
|
||||
@@ -18,12 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//
|
||||
// AMD Wrapper for json2 library
|
||||
//
|
||||
|
||||
/**
|
||||
* Application wrapper
|
||||
*/
|
||||
define([
|
||||
//core
|
||||
'dojo/_base/lang',
|
||||
'dojo/Deferred',
|
||||
'./phases',
|
||||
'./Application_controller',
|
||||
'exports', // for circullar deps
|
||||
'./ipa',
|
||||
'./jquery',
|
||||
'./navigation',
|
||||
@@ -50,78 +54,55 @@ define([
|
||||
'./trust',
|
||||
'./user',
|
||||
'dojo/domReady!'
|
||||
],function(IPA, $) {
|
||||
],function(lang, Deferred, phases, Application_controller, exports) {
|
||||
|
||||
/* main loop (hashchange event handler) */
|
||||
function window_hashchange(evt){
|
||||
IPA.nav.update();
|
||||
}
|
||||
var app = {
|
||||
|
||||
function create_navigation() {
|
||||
var whoami = IPA.whoami;
|
||||
var factory;
|
||||
/**
|
||||
* Application instance
|
||||
*/
|
||||
app: null,
|
||||
|
||||
/**
|
||||
* Application class
|
||||
*/
|
||||
App_class: Application_controller,
|
||||
|
||||
if (whoami.hasOwnProperty('memberof_group') &&
|
||||
whoami.memberof_group.indexOf('admins') !== -1) {
|
||||
factory = IPA.admin_navigation;
|
||||
} else if (whoami.hasOwnProperty('memberofindirect_group')&&
|
||||
whoami.memberofindirect_group.indexOf('admins') !== -1) {
|
||||
factory = IPA.admin_navigation;
|
||||
} else if (whoami.hasOwnProperty('memberof_role') &&
|
||||
whoami.memberof_role.length > 0) {
|
||||
factory = IPA.admin_navigation;
|
||||
} else if (whoami.hasOwnProperty('memberofindirect_role') &&
|
||||
whoami.memberofindirect_role.length > 0) {
|
||||
factory = IPA.admin_navigation;
|
||||
} else {
|
||||
factory = IPA.self_serv_navigation;
|
||||
}
|
||||
/**
|
||||
* Phases registration
|
||||
*/
|
||||
register_phases: function() {
|
||||
|
||||
return factory({
|
||||
container: $('#navigation'),
|
||||
content: $('#content')
|
||||
phases.on('app-init', lang.hitch(this, function() {
|
||||
var app = this.app = new this.App_class();
|
||||
app.init();
|
||||
return app;
|
||||
}));
|
||||
|
||||
phases.on('metadata', lang.hitch(this, function() {
|
||||
var deferred = new Deferred();
|
||||
|
||||
this.app.get_configuration(function(success) {
|
||||
deferred.resolve(success);
|
||||
}, function(error) {
|
||||
deferred.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}));
|
||||
|
||||
function init_on_success(data, text_status, xhr) {
|
||||
$(window).bind('hashchange', window_hashchange);
|
||||
phases.on('profile', lang.hitch(this, function() {
|
||||
this.app.choose_profile();
|
||||
}));
|
||||
},
|
||||
|
||||
var whoami = IPA.whoami;
|
||||
IPA.whoami_pkey = whoami.uid[0];
|
||||
$('#loggedinas .login').text(whoami.cn[0]);
|
||||
$('#loggedinas a').fragment(
|
||||
{'user-facet': 'details', 'user-pkey': IPA.whoami_pkey}, 2);
|
||||
|
||||
$('#logout').click(function() {
|
||||
IPA.logout();
|
||||
return false;
|
||||
}).text(IPA.messages.login.logout);
|
||||
|
||||
$('.header-loggedinas').css('visibility','visible');
|
||||
IPA.update_password_expiration();
|
||||
|
||||
IPA.nav = create_navigation();
|
||||
IPA.nav.create();
|
||||
IPA.nav.update();
|
||||
|
||||
$('#login_header').html(IPA.messages.login.header);
|
||||
}
|
||||
|
||||
|
||||
function init_on_error(xhr, text_status, error_thrown) {
|
||||
var container = $('#content').empty();
|
||||
container.append('<p>Error: '+error_thrown.name+'</p>');
|
||||
container.append('<p>'+error_thrown.message+'</p>');
|
||||
}
|
||||
|
||||
return {
|
||||
run: function() {
|
||||
IPA.init({
|
||||
on_success: init_on_success,
|
||||
on_error: init_on_error
|
||||
});
|
||||
this.register_phases();
|
||||
phases.controller.run();
|
||||
}
|
||||
};
|
||||
|
||||
lang.mixin(exports, app);
|
||||
|
||||
return exports;
|
||||
});
|
||||
245
install/ui/src/freeipa/navigation/Menu.js
Normal file
245
install/ui/src/freeipa/navigation/Menu.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2012 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'dojo/store/Memory',
|
||||
'dojo/_base/array',
|
||||
'dojo/_base/lang',
|
||||
'dojo/store/Observable',
|
||||
'dojo/Evented',
|
||||
'../_base/i18n',
|
||||
'../ipa' // TODO: remove dependance
|
||||
], function(declare, Memory_store, array, lang, Observable, Evented, i18n, IPA) {
|
||||
|
||||
/**
|
||||
* Menu store
|
||||
*
|
||||
* Maintains menu hierarchy and selection state.
|
||||
*/
|
||||
return declare([Evented], {
|
||||
|
||||
/**
|
||||
* Following properties can be specified in menu item spec:
|
||||
* @property {String} name
|
||||
* @property {String} label
|
||||
* @property {String} title
|
||||
* @property {Number} position: position among siblings
|
||||
* @property {menu_item_spec_array} children
|
||||
* @property {String} entity: entity name
|
||||
* @property {String} facet: facet name
|
||||
* @property {Boolean} hidden: menu item is no visible, but can serve for
|
||||
* other evaluations (nested entities)
|
||||
*
|
||||
* Following properties are not in created menu item:
|
||||
* - children
|
||||
*
|
||||
*
|
||||
* Following properties can be stored in menu item at runtime:
|
||||
*
|
||||
* @property {Boolean} selected
|
||||
* @property {String} parent: name of parent menu item
|
||||
* @property {String} selected_child: last selected child. Can be set even
|
||||
* if the child is not selected
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu name
|
||||
* @type String
|
||||
*/
|
||||
name: null,
|
||||
|
||||
/**
|
||||
* Dojo Store of menu items
|
||||
* @type: Store
|
||||
*/
|
||||
items: null,
|
||||
|
||||
/**
|
||||
* Delimiter used in name creation
|
||||
* To avoid having multiple menu items with the same name.
|
||||
* @type String
|
||||
*/
|
||||
path_delimiter: '/',
|
||||
|
||||
/**
|
||||
* Selected menu items
|
||||
* @type Array of menu items
|
||||
*/
|
||||
selected: [],
|
||||
|
||||
/**
|
||||
* Default search options: sort by position
|
||||
*
|
||||
* @type Object
|
||||
*/
|
||||
search_options: { sort: [{attribute:'position'}]},
|
||||
|
||||
/**
|
||||
* Takes a spec of menu item.
|
||||
* Normalizes item's name, parent, adds children if specified
|
||||
*
|
||||
* @param {menu_item} items
|
||||
* @param {String|menu_item} parent
|
||||
* @param {Object} options
|
||||
*/
|
||||
add_item: function(item, parent, options ) {
|
||||
|
||||
item = lang.clone(item); //don't modify original spec
|
||||
|
||||
// each item must have a name and a label
|
||||
// FIXME: consider to move entity and facet stuff outside of this object
|
||||
item.name = item.name || item.facet || item.entity;
|
||||
if (!name) throw 'Missing menu item property: name';
|
||||
if (item.label) item.label = i18n.message(item.label);
|
||||
if (item.title) item.title = i18n.message(item.title);
|
||||
|
||||
if (item.entity) {
|
||||
// FIXME: replace with 'entities' module in future
|
||||
var entity = IPA.get_entity(item.entity);
|
||||
if (!entity) return; //quit
|
||||
//item.name = entity.name;
|
||||
if (!item.label) item.label = entity.label;
|
||||
if (!item.title) item.title = entity.title;
|
||||
} //else if (item.facet) {
|
||||
// TODO: uncomment when facet repository implemented
|
||||
// var facet = facets.(item.facet);
|
||||
// item.name = facet.name;
|
||||
// if (!item.label) item.label = facet.label;
|
||||
// if (!item.title) item.title = facet.title;
|
||||
// }
|
||||
|
||||
item.selected = false;
|
||||
|
||||
// check parent
|
||||
if (typeof parent === 'string') {
|
||||
parent = this.items.get(parent);
|
||||
if (!parent) throw 'Menu item\'s parent doesn\t exist';
|
||||
} else if (typeof parent === 'object') {
|
||||
if (!this.items.getIdentity(parent)) {
|
||||
throw 'Supplied parent isn\'t menu item';
|
||||
}
|
||||
}
|
||||
|
||||
var parent_name = parent ? parent.name : null;
|
||||
var siblings = this.items.query({ parent: parent_name });
|
||||
if (!item.position) item.position = siblings.total;
|
||||
// TODO: add reordering of siblings when position set
|
||||
|
||||
item.parent = parent_name;
|
||||
if (parent) {
|
||||
// names have to be unique
|
||||
item.name = parent.name + this.path_delimiter + item.name;
|
||||
}
|
||||
|
||||
// children will be added separatelly
|
||||
var children = item.children;
|
||||
delete item.children;
|
||||
|
||||
// finally add the item
|
||||
this.items.add(item);
|
||||
|
||||
// add children
|
||||
if (children) {
|
||||
array.forEach(children, function(child) {
|
||||
this.add_item(child, item);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
add_items: function(/* Array */ items) {
|
||||
array.forEach(items, function(item) {
|
||||
this.add_item(item);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query internal data store by using default search options.
|
||||
*
|
||||
* @param Object Query filter
|
||||
* @return QueryResult
|
||||
*/
|
||||
query: function(query) {
|
||||
return this.items.query(query, this.search_options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks item and all its parents as selected.
|
||||
*/
|
||||
_select: function(item) {
|
||||
|
||||
item.selected = true;
|
||||
this.selected.push(item);
|
||||
this.items.put(item);
|
||||
|
||||
if (item.parent) {
|
||||
var parent = this.items.get(item.parent);
|
||||
this._select(parent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects a menu item and all it's ancestors.
|
||||
* @param {string|menu_item} Menu item to select
|
||||
*/
|
||||
select: function(item) {
|
||||
|
||||
if (typeof item == 'string') {
|
||||
item = this.items.get(item);
|
||||
}
|
||||
|
||||
// FIXME: consider to raise an exception
|
||||
if (!item || !this.items.getIdentity(item)) return false;
|
||||
|
||||
// deselect previous
|
||||
var old_selection = lang.clone(this.selected);
|
||||
array.forEach(this.selected, function(mi) {
|
||||
mi.selected = false;
|
||||
this.items.put(mi);
|
||||
}, this);
|
||||
this.selected = [];
|
||||
|
||||
// select new
|
||||
this._select(item);
|
||||
|
||||
var select_state = {
|
||||
item: item,
|
||||
new_selection: this.selected,
|
||||
old_selection: old_selection
|
||||
};
|
||||
|
||||
this.emit('selected', select_state);
|
||||
return select_state;
|
||||
},
|
||||
|
||||
constructor: function(spec) {
|
||||
spec = spec || {};
|
||||
this.items = new Observable( new Memory_store({
|
||||
idProperty: 'name'
|
||||
}));
|
||||
|
||||
spec = lang.clone(spec);
|
||||
this.add_items(spec.items || []);
|
||||
delete spec.items;
|
||||
declare.safeMixin(this, spec);
|
||||
}
|
||||
}); //declare freeipa.menu
|
||||
}); //define
|
||||
337
install/ui/src/freeipa/navigation/Router.js
Normal file
337
install/ui/src/freeipa/navigation/Router.js
Normal file
@@ -0,0 +1,337 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2012 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'dojo/router',
|
||||
'dojo/_base/lang',
|
||||
'dojo/_base/array',
|
||||
'dojo/io-query',
|
||||
'dojo/topic',
|
||||
'../entities',
|
||||
'../facets',
|
||||
'../ipa' //TODO: remove dependancy
|
||||
],
|
||||
function(declare, router, lang, array, ioquery, topic, entities, facets, IPA) {
|
||||
|
||||
/**
|
||||
* Class navigation
|
||||
* This component keeps menu and routes in sync. It signalizes
|
||||
* other components to display facet by sending 'show-facet' event.
|
||||
* Other components can use navigate_to_* methods to change currently
|
||||
* displayed facet. This change can be canceled in 'facet-change'
|
||||
* event handler.
|
||||
*/
|
||||
var navigation = declare(null, {
|
||||
|
||||
/**
|
||||
* Holds references to register route handlers.
|
||||
* Can be used for unregistering routes.
|
||||
* @type Array
|
||||
*/
|
||||
route_handlers: [],
|
||||
|
||||
/**
|
||||
* Prefix of all routes for this navigation. Useful for multiple
|
||||
* navigation objects in one application.
|
||||
* @type String
|
||||
*/
|
||||
route_prefix: '',
|
||||
|
||||
/**
|
||||
* Variations of entity routes
|
||||
*/
|
||||
entity_routes: [
|
||||
'/e/:entity/:facet/:pkeys/*args',
|
||||
'/e/:entity/:facet//*args',
|
||||
'/e/:entity/:facet/:pkeys',
|
||||
'/e/:entity/:facet',
|
||||
'/e/:entity'
|
||||
],
|
||||
|
||||
/**
|
||||
* Variations of simple page routes
|
||||
*/
|
||||
page_routes: [
|
||||
'/p/:page/*args',
|
||||
'/p/:page'
|
||||
],
|
||||
|
||||
/**
|
||||
* Used during facet changing. Set it to true in 'facet-change'
|
||||
* event handler to stop the change.
|
||||
* @type Boolean
|
||||
*/
|
||||
canceled: false,
|
||||
|
||||
/**
|
||||
* Flag to indicate that next hash change should not invoke showing a
|
||||
* facet.
|
||||
* Main purpose: updating hash.
|
||||
* @type Boolen
|
||||
*/
|
||||
ignore_next: false,
|
||||
|
||||
|
||||
/**
|
||||
* Register a route-handler pair to a dojo.router
|
||||
* Handler will be run in context of this object
|
||||
*
|
||||
* @param {string|array} route or routes to register
|
||||
* @param {function} handler to be associated with the route(s)
|
||||
*/
|
||||
register_route: function(route, handler) {
|
||||
// TODO: add multiple routes for one handler
|
||||
route = this.route_prefix + route;
|
||||
this.route_handlers.push(router.register(route, lang.hitch(this, handler)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializates router
|
||||
* - registers handlers
|
||||
*/
|
||||
init_router: function() {
|
||||
|
||||
// entity pages
|
||||
array.forEach(this.entity_routes, function(route) {
|
||||
this.register_route(route, this.entity_route_handler);
|
||||
}, this);
|
||||
|
||||
// special pages
|
||||
this.register_route(this.page_routes, this.page_route_handler);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for entity routes
|
||||
* Shouldn't be invoked directly.
|
||||
*/
|
||||
entity_route_handler: function(event) {
|
||||
|
||||
if (this.check_clear_ignore()) return;
|
||||
|
||||
var entity_name = event.params.entity;
|
||||
var facet_name = event.params.facet;
|
||||
var pkeys = this._decode_pkeys(event.params.pkeys || '');
|
||||
var args = ioquery.queryToObject(event.params.args || '');
|
||||
args.pkeys = pkeys;
|
||||
|
||||
// set new facet state
|
||||
//var entity = entities.get(entity_name);
|
||||
var entity = IPA.get_entity(entity_name); // TODO: replace with prev line
|
||||
var facet = entity.get_facet(facet_name);
|
||||
facet.set_state(args);
|
||||
|
||||
this.show_facet(facet);
|
||||
},
|
||||
|
||||
/**
|
||||
* General facet route handler
|
||||
* Shouldn't be invoked directly.
|
||||
*/
|
||||
page_route_handler: function(event) {
|
||||
|
||||
if (this.check_clear_ignore()) return;
|
||||
|
||||
var facet_name = event.params.page;
|
||||
var args = ioquery.queryToObject(event.params.args || '');
|
||||
|
||||
// // Find menu item
|
||||
// var items = this.menu.items.query({ page: facet_name });
|
||||
//
|
||||
// // Select menu item
|
||||
// if (items.total > 0) {
|
||||
// this.menu.select(items[items.total-1]);
|
||||
// }
|
||||
|
||||
// set new facet state
|
||||
var facet = facets.get(facet_name);
|
||||
facet.set_state(args);
|
||||
|
||||
this.show_facet(facet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Used for switching to entitie's facets. Current target facet
|
||||
* state is used as params (pkeys, args) when none of pkeys and args
|
||||
* are used (useful for switching to previous page with keeping the context).
|
||||
*/
|
||||
navigate_to_entity_facet: function(entity_name, facet_name, pkeys, args) {
|
||||
|
||||
//var entity = entities.get(entity_name);
|
||||
var entity = IPA.get_entity(entity_name); // TODO: replace with prev line
|
||||
var facet = entity.get_facet(facet_name);
|
||||
|
||||
if (!facet) return false; // TODO: maybe replace with exception
|
||||
|
||||
// Use current state if none supplied
|
||||
if (!pkeys && !args) {
|
||||
args = facet.get_state();
|
||||
}
|
||||
args = args || {};
|
||||
|
||||
// Facets may be nested and require more pkeys than supplied.
|
||||
args.pkeys = facet.get_pkeys(pkeys);
|
||||
|
||||
var hash = this._create_entity_facet_hash(facet, args);
|
||||
return this.navigate_to_hash(hash, facet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate to other facet.
|
||||
*/
|
||||
navigate_to_facet: function(facet_name, args) {
|
||||
|
||||
// TODO: uncoment when `facets` are implemented
|
||||
// var facet = facets.get(facet_name);
|
||||
// if (!args) args = facet.get_args();
|
||||
// var hash = this._create_facet_hash(facet, { args: args });
|
||||
// return this.navigate_to_hash(hash, facet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Low level function.
|
||||
*
|
||||
* Public usage should be limited reinitializing canceled navigations.
|
||||
*/
|
||||
navigate_to_hash: function(hash, facet) {
|
||||
|
||||
this.canceled = false;
|
||||
topic.publish('facet-change', { facet: facet, hash: hash });
|
||||
if (this.canceled) {
|
||||
topic.publish('facet-change-canceled', { facet: facet, hash : hash });
|
||||
return false;
|
||||
}
|
||||
this.update_hash(hash, false);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes hash to supplied
|
||||
*
|
||||
* @param {String} Hash to set
|
||||
* @param {Boolean} Whether to suppress following hash change handler
|
||||
*/
|
||||
update_hash: function(hash, ignore_change) {
|
||||
this.ignore_next = !!ignore_change;
|
||||
router.go(hash);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns and resets `ignore_next` property.
|
||||
*/
|
||||
check_clear_ignore: function() {
|
||||
var ignore = this.ignore_next;
|
||||
this.ignore_next = false;
|
||||
return ignore;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates from facet state appropriate hash.
|
||||
*/
|
||||
_create_entity_facet_hash: function(facet, state) {
|
||||
state = lang.clone(state);
|
||||
var entity_name = facet.entity.name;
|
||||
var pkeys = this._encode_pkeys(state.pkeys || []);
|
||||
delete state.pkeys;
|
||||
var args = ioquery.objectToQuery(state || {});
|
||||
|
||||
var hash = [this.route_prefix, 'e', entity_name, facet.name];
|
||||
if (!IPA.is_empty(args)) hash.push(pkeys, args);
|
||||
else if (!IPA.is_empty(pkeys)) hash.push(pkeys);
|
||||
|
||||
hash = hash.join('/');
|
||||
return hash;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates hash of general facet.
|
||||
*/
|
||||
_create_facet_hash: function(facet, state) {
|
||||
var args = ioquery.objectToQuery(state.args || {});
|
||||
var hash = [this.route_prefix, 'p', facet.name];
|
||||
|
||||
if (!IPA.is_empty(args)) hash.push(args);
|
||||
hash = hash.join('/');
|
||||
return hash;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates hash from supplied facet and state.
|
||||
*
|
||||
* @param {facet} facet
|
||||
* @param {Object} state
|
||||
*/
|
||||
create_hash: function(facet, state) {
|
||||
if (facet.entity) return this._create_entity_facet_hash(facet, state);
|
||||
else return this._create_facet_hash(facet, state);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Tells other component to show given facet.
|
||||
*/
|
||||
show_facet: function(facet) {
|
||||
|
||||
topic.publish('facet-show', {
|
||||
facet: facet
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* URI Encodes array items and delimits them by '&'
|
||||
* Example: ['foo ', 'bar'] => 'foo%20&bar'
|
||||
*/
|
||||
_encode_pkeys: function(pkeys) {
|
||||
|
||||
var ret = [];
|
||||
array.forEach(pkeys, function(pkey) {
|
||||
ret.push(encodeURIComponent(pkey));
|
||||
});
|
||||
return ret.join('&');
|
||||
},
|
||||
|
||||
/**
|
||||
* Splits strings by '&' and return an array of URI decoded parts.
|
||||
* Example: 'foo%20&bar' => ['foo ', 'bar']
|
||||
*/
|
||||
_decode_pkeys: function(str) {
|
||||
|
||||
var keys = str.split('&');
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
keys[i] = decodeURIComponent(keys[i]);
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts routing
|
||||
*/
|
||||
startup: function() {
|
||||
router.startup();
|
||||
},
|
||||
|
||||
constructor: function(spec) {
|
||||
spec = spec || {};
|
||||
this.init_router();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return navigation;
|
||||
});
|
||||
107
install/ui/src/freeipa/navigation/menu_spec.js
Normal file
107
install/ui/src/freeipa/navigation/menu_spec.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2012 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define([], function() {
|
||||
|
||||
var nav = {};
|
||||
nav.admin = {
|
||||
name: 'admin',
|
||||
items: [
|
||||
{
|
||||
name: 'identity',
|
||||
label: '@i18n:tabs.identity',
|
||||
children: [
|
||||
{ entity: 'user' },
|
||||
{ entity: 'group' },
|
||||
{ entity: 'host' },
|
||||
{ entity: 'hostgroup' },
|
||||
{ entity: 'netgroup' },
|
||||
{ entity: 'service' },
|
||||
{
|
||||
name:'dns',
|
||||
label: '@i18n:tabs.dns',
|
||||
children: [
|
||||
{ entity: 'dnszone' },
|
||||
{ entity: 'dnsconfig' },
|
||||
{ entity: 'dnsrecord', hidden:true }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{name: 'policy', label: '@i18n:tabs.policy', children: [
|
||||
{name: 'hbac', label: '@i18n:tabs.hbac', children: [
|
||||
{entity: 'hbacrule'},
|
||||
{entity: 'hbacsvc'},
|
||||
{entity: 'hbacsvcgroup'},
|
||||
{entity: 'hbactest'}
|
||||
]},
|
||||
{name: 'sudo', label: '@i18n:tabs.sudo', children: [
|
||||
{entity: 'sudorule'},
|
||||
{entity: 'sudocmd'},
|
||||
{entity: 'sudocmdgroup'}
|
||||
]},
|
||||
{
|
||||
name:'automount',
|
||||
label: '@i18n:tabs.automount',
|
||||
entity: 'automountlocation',
|
||||
children:[
|
||||
{entity: 'automountlocation', hidden:true},
|
||||
{entity: 'automountmap', hidden: true},
|
||||
{entity: 'automountkey', hidden: true}]
|
||||
},
|
||||
{entity: 'pwpolicy'},
|
||||
{entity: 'krbtpolicy'},
|
||||
{entity: 'selinuxusermap'},
|
||||
{name: 'automember', label: '@i18n:tabs.automember',
|
||||
children: [
|
||||
{ name: 'amgroup', entity: 'automember',
|
||||
facet: 'searchgroup', label: '@i18n:objects.automember.usergrouprules'},
|
||||
{ name: 'amhostgroup', entity: 'automember',
|
||||
facet: 'searchhostgroup', label: '@i18n:objects.automember.hostgrouprules'}
|
||||
]}
|
||||
]},
|
||||
{name: 'ipaserver', label: '@i18n:tabs.ipaserver', children: [
|
||||
{name: 'rolebased', label: '@i18n:tabs.role', children: [
|
||||
{entity: 'role'},
|
||||
{entity: 'privilege'},
|
||||
{entity: 'permission'}
|
||||
]},
|
||||
{entity: 'selfservice'},
|
||||
{entity: 'delegation'},
|
||||
{entity: 'idrange'},
|
||||
{entity: 'trust'},
|
||||
{entity: 'config'}
|
||||
]}
|
||||
]
|
||||
};
|
||||
|
||||
nav.self_service = {
|
||||
name: 'self-service',
|
||||
items: [
|
||||
{
|
||||
name: 'identity',
|
||||
label: '@i18n:tabs.identity',
|
||||
children: [{entity: 'user'}]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return nav;
|
||||
});
|
||||
150
install/ui/src/freeipa/navigation2.js
Normal file
150
install/ui/src/freeipa/navigation2.js
Normal file
@@ -0,0 +1,150 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Navigation tells application to show certain facet.
|
||||
*
|
||||
* It's proxy for navigation/Router instace in current running
|
||||
* application.
|
||||
*
|
||||
* Modules just use the interface, they don't have to care about the logic in
|
||||
* the background.
|
||||
*/
|
||||
define([
|
||||
'dojo/_base/lang',
|
||||
'./app', // creates circullar dependency
|
||||
'./ipa',
|
||||
'exports' // for handling circullar dependency
|
||||
],
|
||||
function(lang, app, IPA, exports) {
|
||||
|
||||
|
||||
var get_router = function() {
|
||||
return app.app.router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets property of params depending on arg type following way:
|
||||
* for String sets params.facet
|
||||
* for Facet sets params.facet (based on show function)
|
||||
* for Object sets params.args
|
||||
* for Array sets params.pkeys
|
||||
*
|
||||
* @param Object params
|
||||
* @param {Object|Facet|String|Function} arg
|
||||
*/
|
||||
set_params = function(params, arg) {
|
||||
if (lang.isArray(arg)) {
|
||||
params.pkeys = arg;
|
||||
} else if (typeof arg === 'object') {
|
||||
|
||||
if (typeof arg.show === 'function') params.facet = arg;
|
||||
else params.args = arg;
|
||||
} else if (typeof arg === 'string') {
|
||||
params.facet = arg;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show facet.
|
||||
*
|
||||
* Takes 3 arguments:
|
||||
* * facet(String or Facet)
|
||||
* * pkeys (Array)
|
||||
* * args (Object)
|
||||
*
|
||||
* Argument order is not defined. They are recognized based on their
|
||||
* type.
|
||||
*
|
||||
* When facet is defined as a string it has to be registered in
|
||||
* facet register. //FIXME: not yet implemented
|
||||
*
|
||||
* When it's an object (Facet) and has an entity set it will be
|
||||
* dealt as entity facet.
|
||||
*
|
||||
*/
|
||||
show = function(arg1, arg2, arg3) {
|
||||
|
||||
var nav = get_router();
|
||||
var params = {};
|
||||
|
||||
set_params(params, arg1);
|
||||
set_params(params, arg2);
|
||||
set_params(params, arg3);
|
||||
|
||||
var facet = params.facet;
|
||||
|
||||
if (typeof facet === 'string') {
|
||||
// FIXME: doesn't work at the moment
|
||||
throw 'Not yet supported';
|
||||
//facet = IPA.get_facet(facet);
|
||||
}
|
||||
|
||||
if (!facet) throw 'Argument exception: missing facet';
|
||||
|
||||
if (facet && facet.entity) {
|
||||
return nav.navigate_to_entity_facet(
|
||||
facet.entity.name,
|
||||
facet.name,
|
||||
params.pkeys,
|
||||
params.args);
|
||||
} else {
|
||||
return nav.navigate_to_facet(facet.name, params.args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show entity facet.
|
||||
*
|
||||
* @param String Enity name
|
||||
* @param {Object|Facet|String|Function} arg1
|
||||
* @param {Object|Facet|String|Function} arg2
|
||||
* @param {Object|Facet|String|Function} arg3
|
||||
*
|
||||
* arg1,arg2,arg3 are:
|
||||
* facet name as String
|
||||
* pkeys as Array
|
||||
* args as Object
|
||||
*/
|
||||
show_entity = function(entity_name, arg1, arg2, arg3) {
|
||||
var nav = get_router();
|
||||
var params = {};
|
||||
|
||||
set_params(params, arg1);
|
||||
set_params(params, arg2);
|
||||
set_params(params, arg3);
|
||||
return nav.navigate_to_entity_facet(entity_name, params.facet,
|
||||
params.pkeys, params.args);
|
||||
},
|
||||
|
||||
show_default = function() {
|
||||
// TODO: make configurable
|
||||
return show_entity('user', 'search');
|
||||
};
|
||||
|
||||
// Module export
|
||||
exports = {
|
||||
show: show,
|
||||
show_entity: show_entity,
|
||||
show_default: show_default
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
193
install/ui/src/freeipa/widgets/App.js
Normal file
193
install/ui/src/freeipa/widgets/App.js
Normal file
@@ -0,0 +1,193 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2012 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'dojo/_base/lang',
|
||||
'dojo/_base/array',
|
||||
'dojo/dom',
|
||||
'dojo/dom-construct',
|
||||
'dojo/dom-prop',
|
||||
'dojo/dom-class',
|
||||
'dojo/dom-style',
|
||||
'dojo/query',
|
||||
'dojo/on',
|
||||
'dojo/Evented',
|
||||
'dojo/Stateful',
|
||||
'./Menu',
|
||||
'dojo/NodeList-dom'
|
||||
],
|
||||
function(declare, lang, array, dom, construct, prop, dom_class,
|
||||
dom_style, query, on, Stateful, Evented, Menu) {
|
||||
|
||||
/**
|
||||
* Main application widget
|
||||
*
|
||||
* This class serves as top level widget. It creates basic UI: controls
|
||||
* rendering of header, footer and placeholder for facets.
|
||||
*
|
||||
* @name freeipa.widgets.app
|
||||
* @class
|
||||
*/
|
||||
var app = declare([Stateful, Evented], {
|
||||
|
||||
//widgets
|
||||
menu_widget: null,
|
||||
|
||||
//nodes:
|
||||
|
||||
domNode: null,
|
||||
|
||||
container_node: null,
|
||||
|
||||
background_node: null,
|
||||
|
||||
header_node: null,
|
||||
|
||||
password_expires_node: null,
|
||||
|
||||
logged_nodes: null,
|
||||
|
||||
logged_user_node: null,
|
||||
|
||||
logged_user_link_node: null,
|
||||
|
||||
logout_link_node: null,
|
||||
|
||||
menu_node: null,
|
||||
|
||||
content_node: null,
|
||||
|
||||
app_id: 'container',
|
||||
|
||||
logged: false,
|
||||
|
||||
_loggedSetter: function(value) {
|
||||
this.logged = value;
|
||||
if (this.logged_nodes) {
|
||||
this.logged_nodes.style('visibility', value ? 'visible' : 'hidden');
|
||||
}
|
||||
},
|
||||
|
||||
fullname: '',
|
||||
|
||||
_fullnameSetter: function(value) {
|
||||
this.fullname = value;
|
||||
if (this.logged_user_node) {
|
||||
prop.set(this.logged_user_node, 'textContent', value);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// TODO: this method may be split into several components
|
||||
|
||||
|
||||
this.domNode = construct.create('div', {
|
||||
id: this.app_id
|
||||
});
|
||||
|
||||
if (this.container_node) {
|
||||
construct.place(this.domNode, this.container_node);
|
||||
}
|
||||
|
||||
this._render_background();
|
||||
this._render_header();
|
||||
|
||||
this.menu_node = this.menu_widget.render();
|
||||
construct.place(this.menu_node, this.domNode);
|
||||
|
||||
this.content_node = construct.create('div', {
|
||||
id: 'content'
|
||||
}, this.domNode);
|
||||
},
|
||||
|
||||
_render_background: function() {
|
||||
var inner_html = ''+
|
||||
'<div id="background-header"></div>'+
|
||||
'<div id="background-navigation"></div>'+
|
||||
'<div id="background-left"></div>'+
|
||||
'<div id="background-center"></div>'+
|
||||
'<div id="background-right"></div>';
|
||||
|
||||
this.background_node = construct.create('div', {
|
||||
id: 'background',
|
||||
innerHTML: inner_html
|
||||
}, this.domNode);
|
||||
},
|
||||
|
||||
_render_header: function() {
|
||||
this.header_node = construct.create('div', {
|
||||
id: 'header'
|
||||
}, this.domNode);
|
||||
|
||||
// logo
|
||||
construct.place(''+
|
||||
'<span class="header-logo">'+
|
||||
'<a href="#"><img src="images/ipa-logo.png" />'+
|
||||
'<img src="images/ipa-banner.png" /></a>'+
|
||||
'</span>', this.header_node);
|
||||
|
||||
// right part
|
||||
construct.place(''+
|
||||
'<span class="header-right">'+
|
||||
'<span class="header-passwordexpires"></span>'+
|
||||
'<span id="loggedinas" class="header-loggedinas" style="visibility:hidden;">'+
|
||||
'<a href="#"><span id="login_header">Logged in as</span>: <span class="login"></span></a>'+
|
||||
'</span>'+
|
||||
'<span class="header-loggedinas" style="visibility:hidden;">'+
|
||||
' | <a href="#logout" id="logout">Logout</a>'+
|
||||
'</span>'+
|
||||
'<span id="header-network-activity-indicator" class="network-activity-indicator">'+
|
||||
'<img src="images/spinner-header.gif" />'+
|
||||
'</span>'+
|
||||
'</span>', this.header_node);
|
||||
|
||||
|
||||
this.password_expires_node = query('.header-passwordexpires', this.header_node)[0];
|
||||
this.logged_nodes = query('.header-loggedinas', this.header_node);
|
||||
this.logged_header_node = dom.byId('login_header');// maybe ditch the id?
|
||||
this.logged_user_node = query('#loggedinas .login', this.header_node)[0];
|
||||
this.logged_user_link_node = query('#loggedinas a', this.header_node)[0];
|
||||
this.logout_link_node = dom.byId('logout');
|
||||
|
||||
on(this.logout_link_node, 'click', lang.hitch(this,this.on_logout));
|
||||
on(this.logged_user_link_node, 'click', lang.hitch(this,this.on_profile));
|
||||
|
||||
construct.place(this.header_node, this.domNode);
|
||||
},
|
||||
|
||||
on_profile: function(event) {
|
||||
event.preventDefault();
|
||||
this.emit('profile-click');
|
||||
},
|
||||
|
||||
on_logout: function(event) {
|
||||
event.preventDefault();
|
||||
this.emit('logout-click');
|
||||
},
|
||||
|
||||
constructor: function(spec) {
|
||||
spec = spec || {};
|
||||
this.menu_widget = new Menu();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return app;
|
||||
});
|
||||
271
install/ui/src/freeipa/widgets/Menu.js
Normal file
271
install/ui/src/freeipa/widgets/Menu.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/* Authors:
|
||||
* Petr Vobornik <pvoborni@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2012 Red Hat
|
||||
* see file 'COPYING' for use and warranty information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'dojo/_base/array',
|
||||
'dojo/_base/lang',
|
||||
'dojo/dom',
|
||||
'dojo/dom-construct',
|
||||
'dojo/dom-prop',
|
||||
'dojo/dom-class',
|
||||
'dojo/dom-style',
|
||||
'dojo/dom-attr',
|
||||
'dojo/query',
|
||||
'dojo/Evented',
|
||||
'dojo/on',
|
||||
'../ipa'], function(declare, array, lang, dom, construct, prop, dom_class,
|
||||
dom_style, attr, query, Evented, on, IPA) {
|
||||
|
||||
return declare([Evented], {
|
||||
/**
|
||||
* @name freeipa.widget.menu
|
||||
* @class
|
||||
*
|
||||
* Creates UI for freeipa.navigation.menu. Provides an event when
|
||||
* a menu items is selected.
|
||||
*
|
||||
* event: item-select(menu_item)
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Object store of menu items
|
||||
* @protected
|
||||
* @type freeipa.navigation.menu
|
||||
*/
|
||||
menu: null,
|
||||
|
||||
/**
|
||||
* domNode of this widget. FIXME: move to superclass (none yet)
|
||||
* @type Node
|
||||
*/
|
||||
domNode: null,
|
||||
|
||||
/**
|
||||
* Turns off update on data change
|
||||
* @type Boolen
|
||||
*/
|
||||
ignore_changes: false,
|
||||
|
||||
/**
|
||||
* Css class for nodes containing a submenu of certain level_class
|
||||
* @type String
|
||||
*/
|
||||
level_class: 'menu-level',
|
||||
|
||||
/**
|
||||
* Renders widget's elements
|
||||
*/
|
||||
render: function() {
|
||||
if (this.domNode) {
|
||||
construct.empty(this.domNode);
|
||||
} else {
|
||||
this.domNode = construct.create('div', {
|
||||
'class': 'navigation'
|
||||
});
|
||||
}
|
||||
if (this.menu) {
|
||||
this._render_children(null, this.domNode, 1);
|
||||
}
|
||||
return this.domNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render children of menu_item
|
||||
* Top level items are rendered if menu_items is null
|
||||
*
|
||||
* @protected
|
||||
* @param {menu_item|null} menu_item
|
||||
* @param {Node} node
|
||||
* @param {Number} level
|
||||
*/
|
||||
_render_children: function (menu_item, node, level) {
|
||||
|
||||
var self = this;
|
||||
var name = menu_item ? menu_item.name : null;
|
||||
var children = this.menu.items.query({ parent: name },
|
||||
{ sort: [{attribute:'position'}]});
|
||||
|
||||
var lvl_class = this._get_lvl_class(level);
|
||||
|
||||
if (children.total > 0) {
|
||||
var menu_node = construct.create('div', {
|
||||
'class': 'submenu ' + lvl_class
|
||||
//style: { display: 'none' }
|
||||
});
|
||||
|
||||
if (menu_item) {
|
||||
attr.set(menu_node, 'data-item', menu_item.name);
|
||||
}
|
||||
|
||||
var ul_node = construct.create('ul', null, menu_node);
|
||||
|
||||
array.forEach(children, function(menu_item) {
|
||||
|
||||
var click_handler = function(event) {
|
||||
self.item_clicked(menu_item, event);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
var li_node = construct.create('li', {
|
||||
'data-name': menu_item.name,
|
||||
click: click_handler
|
||||
}, ul_node);
|
||||
|
||||
var a_node = construct.create('a', {
|
||||
click: click_handler
|
||||
}, li_node);
|
||||
|
||||
this._update_item(menu_item, li_node);
|
||||
|
||||
// create submenu
|
||||
this._render_children(menu_item, menu_node, level + 1);
|
||||
}, this);
|
||||
|
||||
construct.place(menu_node, node);
|
||||
}
|
||||
},
|
||||
|
||||
_get_lvl_class: function(level) {
|
||||
return this.level_class + '-' + level;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates content of li_node associated with menu_item base on
|
||||
* menu_item's state.
|
||||
*
|
||||
* @protected
|
||||
* @param {menu_item|string} menu_item
|
||||
* @param {Node} [li_node]
|
||||
*/
|
||||
_update_item: function(menu_item, li_node) {
|
||||
|
||||
if (typeof menu_item === 'string') {
|
||||
menu_item = this.menu.items.get(menu_item);
|
||||
}
|
||||
|
||||
if (!li_node) {
|
||||
li_node = query('li[data-name=\''+menu_item.name+'\']')[0];
|
||||
|
||||
// Quit for non-existing nodes.
|
||||
// FIXME: maybe change to exception
|
||||
if (!li_node) return;
|
||||
}
|
||||
|
||||
dom_class.toggle(li_node, 'disabled', !menu_item.disabled);
|
||||
dom_class.toggle(li_node, 'selected', menu_item.selected);
|
||||
dom_style.set(li_node, {
|
||||
display: menu_item.hidden ? 'none': 'default'
|
||||
});
|
||||
|
||||
var a_node = query('a', li_node)[0];
|
||||
|
||||
prop.set(a_node, 'href', '#' + menu_item.name);
|
||||
prop.set(a_node, 'textContent', menu_item.label);
|
||||
prop.set(a_node, 'title', menu_item.title || menu_item.label);
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays only supplied menu items.
|
||||
* @param {menu_item[]} menu_items Items to show
|
||||
*/
|
||||
select: function(menu_items) {
|
||||
|
||||
// hide all except top level
|
||||
var exception = this._get_lvl_class(1);
|
||||
query('div.submenu', this.domNode).forEach(function(submenu_node) {
|
||||
|
||||
if (dom_class.contains(submenu_node, exception)) return;
|
||||
|
||||
dom_style.set(submenu_node, {
|
||||
display: 'none'
|
||||
});
|
||||
}, this);
|
||||
|
||||
// show and update selected
|
||||
array.forEach(menu_items, function(item) {
|
||||
this._update_item(item);
|
||||
|
||||
// show submenu
|
||||
var item_div = query('div[data-item=\''+item.name+'\']', this.domNode)[0];
|
||||
if (item_div) {
|
||||
dom_style.set(item_div, {
|
||||
display: 'block'
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles changes in this.menu object.
|
||||
*
|
||||
* @protected
|
||||
* @param {menu_item} object
|
||||
* @param {Number} removedFrom
|
||||
* @param {Number} insertedInto
|
||||
*/
|
||||
_items_changed: function(object, removedFrom, insertedInto) {
|
||||
|
||||
if (this.ignore_changes) return;
|
||||
|
||||
if (removedFrom === -1 && insertedInto === -1) {
|
||||
this._update_item(object);
|
||||
} else {
|
||||
// on add or removal, replace whole menu
|
||||
this.render();
|
||||
this.select(this.menu.selected);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets this.menu and starts to watch its changes
|
||||
* @param {freeipa.navigation.menu} menu
|
||||
*/
|
||||
set_menu: function(menu) {
|
||||
this.menu = menu;
|
||||
//get all items
|
||||
var q = menu.items.query();
|
||||
q.observe(lang.hitch(this, this._items_changed), true);
|
||||
on(this.menu, 'selected', lang.hitch(this, function(event) {
|
||||
this.select(event.new_selection);
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal handler for clicking on menu item.
|
||||
* Raises item-select event.
|
||||
*/
|
||||
_item_clicked: function(menu_item) {
|
||||
this.emit('item-select', menu_item);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles click on menu item.
|
||||
*
|
||||
* Intended for overriding.
|
||||
*
|
||||
* @param {menu_item} menu_item
|
||||
* @param {Event} event
|
||||
*/
|
||||
item_clicked: function(menu_item/*, event*/) {
|
||||
this._item_clicked(menu_item);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user