sku-pg
Version:
Skulpt is a Javascript implementation of Python 2.x. Python that runs in your browser!
928 lines (774 loc) • 23 kB
JavaScript
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A basic menu that accepts a set of items. The ITEM_EVENT
* instead of returning the DOM node returns a reference to the menu item.
*
* NOTE: This class has been deprecated. Please use goog.ui.Menu,
* goog.ui.PopupMenu, and if you need submenus goog.ui.SubMenu.
*
*
*
*
*/
goog.provide('goog.ui.BasicMenu');
goog.provide('goog.ui.BasicMenu.Item');
goog.provide('goog.ui.BasicMenu.Separator');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.a11y');
goog.require('goog.positioning');
goog.require('goog.positioning.AnchoredPosition');
goog.require('goog.positioning.Corner');
goog.require('goog.ui.AttachableMenu');
goog.require('goog.ui.ItemEvent');
/**
* Class that extends AttachableMenu and creates a simple menu, purely from
* code.The ITEM_EVENT instead of returning the DOM node returns a reference the
* menu item.
*
* @param {string=} opt_class Optional class for menu element, Default: 'menu'.
* @param {Element=} opt_parent Optional parent element, otherwise it will be
* added to the end of the document body.
* @constructor
* @extends {goog.ui.AttachableMenu}
*/
goog.ui.BasicMenu = function(opt_class, opt_parent) {
var cls = opt_class || 'menu';
var parent = opt_parent || goog.dom.getDocument().body;
/**
* Menu element
* @type {Element}
* @private
*/
this.element_ = goog.dom.createDom('div', {'tabIndex': 0, 'class': cls});
goog.dom.a11y.setRole(this.element_, 'menu');
goog.dom.a11y.setState(this.element_, 'haspopup', true);
parent.appendChild(this.element_);
goog.ui.AttachableMenu.call(this, this.element_);
/**
* Parent menu
* @type {goog.ui.BasicMenu}
* @private
*/
this.parentMenu_ = null;
/**
* Array of menu items
* @type {Array}
* @private
*/
this.items_ = [];
/**
* The active item.
* @type {goog.ui.BasicMenu.Item}
* @private
*/
this.activeItem_;
};
goog.inherits(goog.ui.BasicMenu, goog.ui.AttachableMenu);
/**
* Key for the event used to trigger the menu
* @type {?number}
* @private
*/
goog.ui.BasicMenu.prototype.evtKey_ = null;
/**
* Key for the window resize event listener
* @type {?number}
* @private
*/
goog.ui.BasicMenu.prototype.resizeEvtKey_ = null;
/**
* Z-index used for top level menu.
* @type {number}
* @private
*/
goog.ui.BasicMenu.prototype.zIndex_ = 10;
/**
* A time in ms used to delay opening/closing submenus when the selection
* changes.
*
* @type {number}
* @private
*/
goog.ui.BasicMenu.SUBMENU_ACTIVATION_DELAY_MS_ = 300;
/**
* @return {number} The z-index.
*/
goog.ui.BasicMenu.prototype.getZIndex = function() {
return this.zIndex_;
};
/**
* Sets the z-index. The change will take effect the next time
* setVisible(true) is called.
*
* @param {number} zIndex The new z-index.
*/
goog.ui.BasicMenu.prototype.setZIndex = function(zIndex) {
this.zIndex_ = zIndex;
};
/**
* Add a menu item.
*
* @param {goog.ui.BasicMenu.Item} item Menu Item.
*/
goog.ui.BasicMenu.prototype.add = function(item) {
var el = this.getElement();
if (!el) {
throw Error('setElement() called before create()')
}
if (item.getMenu()) {
throw Error('Menu item already added to a menu')
}
item.setMenu_(this);
this.items_.push(item);
el.appendChild(item.create());
};
/**
* Add a menu item at a specific index.
* @param {goog.ui.BasicMenu.Item} item Menu Item.
* @param {number} index The index to insert at.
*/
goog.ui.BasicMenu.prototype.insertAt = function(item, index) {
var el = this.getElement();
if (!el) {
throw Error('setElement() called before create()')
}
if (item.getMenu()) {
throw Error('Menu item already added to a menu')
}
item.setMenu_(this);
goog.array.insertAt(this.items_, item, index);
el.insertBefore(item.create(), el.childNodes[index]);
};
/**
* Remove a menu item.
* @param {goog.ui.BasicMenu.Item} item Menu Item.
*/
goog.ui.BasicMenu.prototype.remove = function(item) {
item.remove();
item.setMenu_(null);
goog.array.remove(this.items_, item);
};
/**
* Remove a menu item from a particular index.
* @param {number} index Index of menu item to remove.
*/
goog.ui.BasicMenu.prototype.removeAt = function(index) {
this.remove(this.items_[index]);
};
/**
* Sets focus to the menu's base element.
*/
goog.ui.BasicMenu.prototype.focus = function() {
this.element_.focus();
};
/**
* Sets menu's parent menu in case it's a submenu.
* @param {goog.ui.BasicMenu} parent Parent menu.
* @private
*/
goog.ui.BasicMenu.prototype.setParentMenu_ = function(parent) {
this.parentMenu_ = parent;
this.setParentEventTarget(parent);
};
/**
* @return {goog.ui.BasicMenu} The menu's parent menu in case it's a submenu.
*/
goog.ui.BasicMenu.prototype.getParentMenu = function() {
return this.parentMenu_;
};
/**
* Anchor the menu position to an element, and attach a click event.
* @param {Element} el Element to anchor menu to.
* @param {goog.positioning.Corner=} opt_pos Corner: Default Bottom-left.
* @param {goog.events.EventType=} opt_eventType Event that triggers menu.
* Default click.
*/
goog.ui.BasicMenu.prototype.setAnchorElement = function(el, opt_pos,
opt_eventType) {
if (this.evtKey_) {
goog.events.unlistenByKey(this.evtKey_);
}
// Reset properties related to showing/hiding the menu as their state is only
// applicable when bound to the anchor element original triggering the menu.
if (this.anchorElement_ != el) {
this.clickToClose_ = false;
this.lastHideTime_ = -1;
}
var eventType = opt_eventType || goog.events.EventType.CLICK;
this.evtKey_ = goog.events.listen(el, eventType, this.openMenu_, false, this);
this.resizeEvtKey_ = goog.events.listen(window,
goog.events.EventType.RESIZE, this.onResize_, false, this);
this.setPosition(new goog.positioning.AnchoredPosition(el,
goog.isDef(opt_pos) ? opt_pos : goog.positioning.Corner.BOTTOM_START));
this.anchorElement_ = el;
};
/** @inheritDoc */
goog.ui.BasicMenu.prototype.disposeInternal = function() {
for (var i = 0; i < this.items_.length; i++) {
this.items_[i].dispose();
}
goog.events.unlistenByKey(this.evtKey_);
goog.events.unlistenByKey(this.resizeEvtKey_);
goog.dom.removeNode(this.element_);
delete this.element_;
this.anchorElement_ = null;
goog.ui.BasicMenu.superClass_.disposeInternal.call(this);
};
/**
* Sets whether the popup should be visible.
* @param {boolean} visible Show menu?.
* @param {boolean=} opt_bubble Bubble to parent menu?.
*/
goog.ui.BasicMenu.prototype.setVisible = function(visible, opt_bubble) {
// Ignore setVisible(true) if already visible
if (this.isOrWasRecentlyVisible() && visible) {
return;
}
if (visible == false) {
// Close submenus recursively. goog.ui.BasicMenu.Item.closeSubmenu calls
// goog.ui.BasicMenu.setVisible(false) for the submenu.
if (this.activeItem_) {
this.activeItem_.closeSubmenu();
goog.ui.AttachableMenu.prototype.setSelectedItem.call(this, null);
}
// Set focus to parent for submenus.
if (this.parentMenu_ && !opt_bubble) {
this.parentMenu_.focus();
}
}
else {
if (this.parentMenu_) {
this.zIndex_ = this.parentMenu_.getZIndex() + 1;
}
this.element_.style.zIndex = this.zIndex_;
}
if (opt_bubble && this.parentMenu_) {
this.parentMenu_.setVisible(visible, opt_bubble);
}
// Clear activation timer
if (this.activationTimer_) {
window.clearTimeout(this.activationTimer_);
this.activationTimer_ = null;
}
this.activeItem_ = null;
goog.ui.PopupBase.prototype.setVisible.call(this, visible);
};
/**
* Select menu item by index.
* @param {number} index Index of item to select, zero based.
*/
goog.ui.BasicMenu.prototype.setSelectedIndex = function(index) {
this.setSelectedItem(index == -1 ? null : this.element_.childNodes[index]);
};
/**
* Select menu item by element reference and active it (open/close submenus)
* with a slight delay.
* @param {Element} el Element for item to select.
* @param {boolean=} opt_keyEvent Was item selected using keyboard? In that case
* open submenus are closed immediately and new submenus are not opened
* automatically.
* @private
*/
goog.ui.BasicMenu.prototype.itemSelectionHandler_ = function(el, opt_keyEvent) {
// Highlight menu item
if (el || !this.activeItem_ || !this.activeItem_.hasOpenSubmenu()) {
goog.ui.AttachableMenu.prototype.setSelectedItem.call(this, el);
}
var item = el ? this.getItemForElement_(el) : null;
if (item && item != this.activeItem_) {
if (opt_keyEvent && this.activeItem_) {
this.activeItem_.closeSubmenu();
this.activeItem_ = null;
}
// Clear previous timer, if any
if (this.activationTimer_) {
window.clearTimeout(this.activationTimer_);
this.activationTimer_ = null;
}
// Call selectItem_ with delay
if (!opt_keyEvent) {
this.activationTimer_ = window.setTimeout(
goog.bind(this.selectItem_, this, item),
goog.ui.BasicMenu.SUBMENU_ACTIVATION_DELAY_MS_);
}
// Select anchor element in parent menu (to prevent submenu from closing).
if (this.parentMenu_) {
this.parentMenu_.setSelectedItem(this.anchorElement_);
this.element_.focus();
}
}
};
/**
* Select menu item by element reference and activate it immediately.
* @param {Element|goog.ui.BasicMenu.Item} arg Element Item to select or element
* for it.
*/
goog.ui.BasicMenu.prototype.setSelectedItem = function(arg) {
var el, item;
if (!arg) {
el = null;
item = null;
} else if (arg instanceof goog.ui.BasicMenu.Item) {
item = arg;
el = item.element_;
} else {
el = arg;
item = this.getItemForElement_(el);
}
if (el || !this.activeItem_ || !this.activeItem_.hasOpenSubmenu()) {
goog.ui.AttachableMenu.prototype.setSelectedItem.call(this, el);
}
if (item == this.activeItem_) {
return;
}
if (this.activeItem_ && el) {
this.activeItem_.closeSubmenu();
}
if (el) {
// TODO(user): var item declared earlier
item = this.getItemForElement_(el);
if (item.hasSubmenu()) {
item.openSubmenu();
item.getSubmenu().focus();
}
this.activeItem_ = item;
}
};
/**
* @return {goog.ui.BasicMenu.Item} The selected item.
*/
goog.ui.BasicMenu.prototype.getSelectedItem = function() {
return this.selectedElement_ ? this.items_[goog.array.indexOf(
this.element_.childNodes, this.selectedElement_)] : null;
};
/**
* Select menu item, triggered by a delayed call from itemSelectionHandler_.
* Opens submenu associated with selected/active item and/or closes any other
* open submenus.
* @param {goog.ui.BasicMenu.Item} item Menu item to select.
* @private
*/
goog.ui.BasicMenu.prototype.selectItem_ = function(item) {
// Clear timer
if (this.activationTimer_) {
window.clearTimeout(this.activationTimer_);
this.activationTimer_ = null;
}
var selectedItem = this.getItemForElement_(this.selectedElement_);
if (selectedItem != item) {
return;
}
if (this.activeItem_ && item) {
this.activeItem_.closeSubmenu();
}
if (item.hasSubmenu()) {
item.openSubmenu();
item.getSubmenu().focus();
}
else {
this.element_.focus();
}
this.activeItem_ = item;
};
/**
* Activates a menu item, opens submenu or triggers the select event and closes
* the menu if no submenu is available for item.
* @param {Element} el Element for item to activate.
* @private
*/
goog.ui.BasicMenu.prototype.activateItem_ = function(el) {
var item = this.getItemForElement_(el);
if (item.hasSubmenu()) {
item.openSubmenu();
var submenu = item.getSubmenu();
submenu.focus();
this.activeItem_ = item;
}
else {
this.setVisible(false, true);
this.dispatchEvent(new goog.ui.ItemEvent(
goog.ui.MenuBase.Events.ITEM_ACTION, this, item));
}
};
/**
* Anchor triggered, open menu unless it was just closed by the mousedown part
* of the click.
*
* @private
*/
goog.ui.BasicMenu.prototype.openMenu_ = function() {
if (!this.clickToClose_) {
this.setVisible(true);
}
this.clickToClose_ = false;
};
/**
* Returns whether the specified element is contained inside the menu, including
* open submenus.
* @param {Element} el Element to check.
* @return {boolean} Whether the specified element is contained inside the menu,
* including open submenus.
* @private
*/
goog.ui.BasicMenu.prototype.containsElement_ = function(el) {
if (goog.dom.contains(this.element_, el)) {
return true;
}
if (this.activeItem_ && this.activeItem_.hasSubmenu()) {
return this.activeItem_.getSubmenu().containsElement_(el);
}
return false;
};
/**
* Mouse down handler for the document on capture phase. Hides the menu.
* @param {goog.events.BrowserEvent} e The event object.
* @private
*/
goog.ui.BasicMenu.prototype.onDocumentMouseDown_ = function(e) {
// Mousedown on anchor element, set clickToClose_ to true to prevent the
// mouseup event from opening the menu.
if (this.anchorElement_ == e.target ||
goog.dom.contains(this.anchorElement_, e.target)) {
this.clickToClose_ = true;
}
// Mousedown outside menu, close it.
var rootMenu = this;
while (rootMenu.parentMenu_) {
rootMenu = rootMenu.parentMenu_;
}
if (!rootMenu.containsElement_(/** @type {Element} */ (e.target))) {
this.hide_();
}
};
/**
* Mouse over handler for the menu.
* @param {goog.events.Event} e The event object.
* @protected
*/
goog.ui.BasicMenu.prototype.onMouseOver = function(e) {
var eltItem = this.getAncestorMenuItem_(/** @type {Element} */ (e.target));
if (eltItem == null) {
return;
}
this.itemSelectionHandler_(eltItem);
};
/**
* Mouse out handler for the menu.
* @param {goog.events.Event} e The event object.
* @protected
*/
goog.ui.BasicMenu.prototype.onMouseOut = function(e) {
var eltItem = this.getAncestorMenuItem_(/** @type {Element} */ (e.target));
if (eltItem == null) {
return;
}
this.itemSelectionHandler_(null);
};
/**
* Overloaded document focus handler. Prevents the default action which is to
* close the menu on focus change, which is not desirable for hierarchical
* menus.
* @param {goog.events.Event} e The event object.
* @private
*/
goog.ui.BasicMenu.prototype.onDocumentFocus_ = function(e) {
};
/**
* Mouse up handler for the menu.
* @param {goog.events.Event} e The event object.
* @protected
*/
goog.ui.BasicMenu.prototype.onMouseUp = function(e) {
var eltItem = this.getAncestorMenuItem_(/** @type {Element} */ (e.target));
if (eltItem != null) {
this.activateItem_(eltItem);
}
};
/**
* Window resize handler.
* @private
*/
goog.ui.BasicMenu.prototype.onResize_ = function() {
if (!this.isDisposed() && this.isVisible()){
this.reposition();
}
};
/**
* Key down handler for the menu.
* @param {goog.events.KeyEvent} e The event object.
* @protected
*/
goog.ui.BasicMenu.prototype.onKeyDown = function(e) {
var handled = false;
switch (e.keyCode) {
case 37: // Left
if (this.parentMenu_) {
this.setVisible(false); // setVisible(false) calls focus on the parent
}
handled = true;
break;
case 39: // Right
var item = this.getItemForElement_(this.selectedElement_);
if (this.selectedElement_ && item.hasSubmenu()) {
this.activateItem_(this.selectedElement_);
item.getSubmenu().setSelectedIndex(0);
}
handled = true;
break;
case 40: // Down
this.itemSelectionHandler_(this.getNextPrevItem(false), true);
handled = true;
break;
case 38: // Up
this.itemSelectionHandler_(this.getNextPrevItem(true), true);
handled = true;
break;
case 13: // Enter
if (this.selectedElement_) {
this.activateItem_(this.selectedElement_);
}
handled = true;
break;
case 27: // Esc
this.setVisible(false);
handled = true;
break;
}
// Prevent the browser's default keydown behaviour when the menu is open,
// e.g. keyboard scrolling
if (handled) {
e.preventDefault();
}
};
/**
* Called after the menu is shown.
* @protected
* @suppress {underscore}
* @override
*/
goog.ui.BasicMenu.prototype.onShow_ = function() {
goog.ui.BasicMenu.superClass_.onShow_.call(this);
this.setSelectedItem(null);
var rtl = goog.style.isRightToLeft(this.element_);
goog.dom.classes.enable(this.element_, goog.getCssName('goog-rtl'), rtl);
if (!this.parentMenu_) {
this.element_.focus();
}
};
/**
* Returns the menu item a given element is associated with.
* @param {Element} el Element.
* @return {goog.ui.BasicMenu.Item} The menu item a given element is associated
* with.
* @private
*/
goog.ui.BasicMenu.prototype.getItemForElement_ = function(el) {
var index = -1;
for (var node = el; node; node = goog.dom.getPreviousElementSibling(node)) {
index++;
}
return index == -1 ? null : this.items_[index];
};
/**
* A menu item
*
* @param {?string} caption Html caption that gets shown in the menu.
* @param {Object=} opt_value The value that gets returned in the ItemEvent.
* @param {goog.ui.BasicMenu=} opt_submenu Optional menu that this item is the
* anchor for.
* @constructor
* @extends {goog.Disposable}
*/
goog.ui.BasicMenu.Item = function(caption, opt_value, opt_submenu) {
goog.Disposable.call(this);
/**
* HTML Caption that gets displayed in the menu
* @type {string}
* @private
*/
this.caption_ = String(caption);
/**
* Value associated with the menu option.
* @type {*}
* @private
*/
this.value_ = opt_value || caption;
/**
* Reference to the sub menu that this item is the anchor for.
* @type {goog.ui.BasicMenu}
* @private
*/
this.submenu_ = opt_submenu || null;
/**
* Reference to the menu that this item is attached to.
* @type {goog.ui.BasicMenu}
* @private
*/
this.menu_ = null;
/**
* Menu item element
* @type {Element}
* @private
*/
this.element_ = null;
};
goog.inherits(goog.ui.BasicMenu.Item, goog.Disposable);
/**
* @return {string} The caption.
*/
goog.ui.BasicMenu.Item.prototype.getCaption = function() {
return this.caption_;
};
/**
* @return {*} The value associated with menu item.
*/
goog.ui.BasicMenu.Item.prototype.getValue = function() {
return this.value_;
};
/**
* Updates caption.
* @param {string} caption Desired caption.
*/
goog.ui.BasicMenu.Item.prototype.setCaption = function(caption) {
this.caption_ = caption;
if (this.element_) {
this.element_.firstChild.nodeValue = caption;
}
};
/**
* Sets value associated with menu item.
* @param {Object} value Desired value.
*/
goog.ui.BasicMenu.Item.prototype.setValue = function(value) {
this.value_ = value;
};
/** @inheritDoc */
goog.ui.BasicMenu.Item.prototype.disposeInternal = function() {
goog.ui.BasicMenu.Item.superClass_.disposeInternal.call(this);
this.remove();
if (this.submenu_) {
this.submenu_.dispose();
}
};
/**
* Set the parent menu for this menu item.
* @param {goog.ui.BasicMenu} menu Parent menu.
* @private
*/
goog.ui.BasicMenu.Item.prototype.setMenu_ = function(menu) {
this.menu_ = menu;
if (this.submenu_) {
this.submenu_.setParentMenu_(menu);
}
};
/**
* @return {goog.ui.BasicMenu} The parent menu for this menu item.
* @protected
*/
goog.ui.BasicMenu.Item.prototype.getMenu = function() {
return this.menu_;
};
/**
* Returns the DOM element(s) for the menu item. Should be treated as package
* scope.
* @return {Element} The DOM element(s) for the menu item.
*/
goog.ui.BasicMenu.Item.prototype.create = function() {
if (!this.menu_) {
throw Error('MenuItem is not attached to a menu');
}
var leftArrow, rightArrow;
if (this.submenu_) {
rightArrow = goog.dom.createDom('span',
goog.getCssName('goog-menu-arrow-right'), '\u25b6');
leftArrow = goog.dom.createDom('span',
goog.getCssName('goog-menu-arrow-left'), '\u25c0');
}
this.element_ = goog.dom.createDom('div', this.menu_.getItemClassName(),
this.caption_, leftArrow, rightArrow);
return this.element_;
};
/**
* Removes DOM element(s) for item.
*/
goog.ui.BasicMenu.Item.prototype.remove = function() {
goog.dom.removeNode(this.element_);
this.element_ = null;
};
/**
* @return {boolean} Whether the menu item has has submenu.
*/
goog.ui.BasicMenu.Item.prototype.hasSubmenu = function() {
return this.submenu_ != null;
};
/**
* @return {boolean} Whether the menu item has has submenu that's open.
*/
goog.ui.BasicMenu.Item.prototype.hasOpenSubmenu = function() {
return this.hasSubmenu() ? this.submenu_.isOrWasRecentlyVisible() : false;
};
/**
* @return {goog.ui.BasicMenu} The submenu associated with the item.
*/
goog.ui.BasicMenu.Item.prototype.getSubmenu = function() {
return this.submenu_;
};
/**
* Opens the item's submenu.
*/
goog.ui.BasicMenu.Item.prototype.openSubmenu = function() {
if (this.submenu_) {
var submenu = this.submenu_;
// If the submenu is pinned at a TOP position, infer
// that we want to anchor at the opposite corresponding
// absolute/relative horizontal position.
var pinComplement = goog.positioning.flipCornerHorizontal(
submenu.getPinnedCorner());
submenu.setAnchorElement(this.element_, pinComplement);
submenu.setVisible(true);
}
};
/**
* Closes the item's submenu.
*/
goog.ui.BasicMenu.Item.prototype.closeSubmenu = function() {
if (this.submenu_) {
this.submenu_.setVisible(false);
}
};
/**
* A menu separator
*
* @constructor
* @extends {goog.ui.BasicMenu.Item}
*/
goog.ui.BasicMenu.Separator = function() {
goog.ui.BasicMenu.Item.call(this, null);
};
goog.inherits(goog.ui.BasicMenu.Separator, goog.ui.BasicMenu.Item);
/**
* Returns the DOM element(s) for the separator. Should be treated as having
* package scope.
* @return {Element} The DOM element(s) for the separator.
*/
goog.ui.BasicMenu.Separator.prototype.create = function() {
if (!this.menu_) {
throw Error('MenuSeparator is not attached to a menu');
}
this.element_ = goog.dom.createElement('hr');
goog.dom.a11y.setRole(this.element_, 'separator');
return this.element_;
};