📄 sync.menu.js
字号:
/** * Abstract base class for menu rendering peers. */Extras.Sync.Menu = Core.extend(Echo.Render.ComponentSync, { $static: { DEFAULT_FOREGROUND: "#000000", DEFAULT_BACKGROUND: "#cfcfcf", DEFAULT_DISABLED_FOREGROUND: "#7f7f7f", DEFAULT_SELECTION_FOREGROUND: "#ffffff", DEFAULT_SELECTION_BACKGROUND: "#3f3f3f", DEFAULT_BORDER: "1px outset #cfcfcf", MAX_Z_INDEX: 65535 }, /** * The root menu model. * @type Extras.MenuModel */ menuModel: null, /** * The menu state model. * @type Extras.MenuStateModel */ stateModel: null, /** * The root DOM element of the rendered menu. * @type Element */ element: null, /** * The active state of the menu, true when the menu is open. * @type Boolean */ active: false, /** * Array containing RenderedState objects for open menus. * @type Boolean */ _openMenuPath: null, /** * Flag indicating whether menu mask is deployed. * @type Boolean */ _maskDeployed: false, /** * Reference to the mask click listener. */ _processMaskClickRef: null, /** Reference to menu key press listener. */ _processKeyPressRef: null, /** * The collection of named overlay elements (top/left/right/bottom) deployed to cover non-menu elements of the * screen with transparent DIVs when the menu is active. This allows the menu to receive de-activation events, * event if a mouse click is received in an IFRAME document. */ _overlay: null, $construct: function() { this._processMaskClickRef = Core.method(this, this._processMaskClick); this._processKeyPressRef = Core.method(this, this.processKeyPress); this._openMenuPath = []; }, $abstract: { /** * Returns an object containing 'x' and 'y' properties indicating the position at * which a submenu should be placed. */ getSubMenuPosition: function(menuModel, width, height) { }, /** * Renders the top level menu of the menu component (that which resides in the DOM at all times). * * @param {Echo.Update.ComponentUpdate} the hierarchy update for which the rendering is being performed */ renderMain: function(update) { } }, $virtual: { /** * Activates the menu component. */ activate: function() { if (this.active) { return false; } this.component.set("modal", true); this.active = true; this.addMask(); Core.Web.DOM.focusElement(this.element); Core.Web.Event.add(this.element, Core.Web.Env.QUIRK_IE_KEY_DOWN_EVENT_REPEAT ? "keydown" : "keypress", this._processKeyPressRef, true); return true; }, /** * Activates a menu item. Displays submenu if item is a submenu. Invokes menu action if item * is a menu option. * * @param {Extras.ItemModel} itemModel the item model to activate. */ activateItem: function(itemModel) { if (this.stateModel && !this.stateModel.isEnabled(itemModel.modelId)) { return; } if (itemModel instanceof Extras.OptionModel) { this.deactivate(); this.processAction(itemModel); } else if (itemModel instanceof Extras.MenuModel) { this._openMenu(itemModel); } }, /** * Fires an action event in response to a menu option being activated. */ processAction: function(itemModel) { this.component.doAction(itemModel); }, /** * Key press listener. */ processKeyPress: function(e) { if (e.keyCode == 27) { this.deactivate(); return false; } return true; } }, addMenu: function(menu) { this._openMenuPath.push(menu); }, /** * Adds the menu mask, such that click events on elements other than the menu will be captured by the menu. */ addMask: function() { if (this.maskDeployed) { return; } this.maskDeployed = true; this._overlayAdd(new Core.Web.Measure.Bounds(this.element)); Core.Web.Event.add(document.body, "click", this._processMaskClickRef, false); Core.Web.Event.add(document.body, "contextmenu", this._processMaskClickRef, false); }, /** * Closes all open menus. */ closeAll: function() { while (this._openMenuPath.length > 0) { var menu = this._openMenuPath.pop(); menu.close(); } }, /** * Closes all open menus which are descendants of the specified parent menu. * * @param {Extras.MenuModel} the parent menu */ closeDescendants: function(parentMenu) { while (parentMenu != this._openMenuPath[this._openMenuPath.length - 1]) { var menu = this._openMenuPath.pop(); menu.close(); } }, /** * Deactivates the menu component, closing any open menus. */ deactivate: function() { this.component.set("modal", false); if (!this.active) { return; } this.active = false; Core.Web.Event.remove(this.element, Core.Web.Env.QUIRK_IE_KEY_DOWN_EVENT_REPEAT ? "keydown" : "keypress", this._processKeyPressRef, true); this.closeAll(); this.removeMask(); }, /** * Determines if the specified menu is currently open (on-screen). * * @param {Extras.MenuModel} menuModel the menu * @return true if the menu is open * @type Boolean */ isOpen: function(menuModel) { for (var i = 0; i < this._openMenuPath.length; ++i) { if (this._openMenuPath[i].menuModel == menuModel) { return true; } } return false; }, /** * Creates and adds the overlay mask elements to the screen, blocking all content except that within the specified bounds. * * @param bounds an object containing the pixel bounds of the region NOT to be blocked, must provide top, left, width, and * height integer properties */ _overlayAdd: function(bounds) { this._overlayRemove(); var bottom = bounds.top + bounds.height, right = bounds.left + bounds.width, domainBounds = new Core.Web.Measure.Bounds(this.client.domainElement); this._overlay = { }; if (bounds.top > 0) { this._overlay.top = document.createElement("div"); this._overlay.top.style.cssText = "position:absolute;z-index:32767;top:0;left:0;width:100%;" + "height:" + bounds.top + "px;"; this.client.domainElement.appendChild(this._overlay.top); } if (bottom < domainBounds.height) { this._overlay.bottom = document.createElement("div"); this._overlay.bottom.style.cssText = "position:absolute;z-index:32767;bottom:0;left:0;width:100%;" + "top:" + bottom + "px;"; this.client.domainElement.appendChild(this._overlay.bottom); } if (bounds.left > 0) { this._overlay.left = document.createElement("div"); this._overlay.left.style.cssText = "position:absolute;z-index:32767;left:0;" + "width:" + bounds.left + "px;top:" + bounds.top + "px;height:" + bounds.height + "px;"; this.client.domainElement.appendChild(this._overlay.left); } if (right < domainBounds.width) { this._overlay.right = document.createElement("div"); this._overlay.right.style.cssText = "position:absolute;z-index:32767;right:0;" + "left:" + right + "px;top:" + bounds.top + "px;height:" + bounds.height + "px;"; this.client.domainElement.appendChild(this._overlay.right); } for (var name in this._overlay) { Echo.Sync.FillImage.render(this.client.getResourceUrl("Echo", "resource/Transparent.gif"), this._overlay[name]); Core.Web.VirtualPosition.redraw(this._overlay[name]); } }, /** * Removes the overlay mask from the screen, if present. */ _overlayRemove: function() { if (!this._overlay) { return; } for (var name in this._overlay) { this.client.domainElement.removeChild(this._overlay[name]); } this._overlay = null; }, /** * Opens a menu. * * @param {Extras.MenuModel} menuModel the menu to open */ _openMenu: function(menuModel) { if (this.isOpen(menuModel)) { return; } var subMenu = new Extras.Sync.Menu.RenderedMenu(this, menuModel); subMenu.create(); var parentMenu = null; for (var i = 0; i < this._openMenuPath.length; ++i) { if (this._openMenuPath[i].menuModel == menuModel.parent) { parentMenu = this._openMenuPath[i]; break; } } if (parentMenu == null) { parentMenu = this; } else { this.closeDescendants(parentMenu); } var position = parentMenu.getSubMenuPosition(menuModel, subMenu.width, subMenu.height); var windowBounds = new Core.Web.Measure.Bounds(document.body); if (position.x + subMenu.width > windowBounds.width) { position.x = windowBounds.width - subMenu.width; if (position.x < 0) { position.x = 0; } } if (position.y + subMenu.height > windowBounds.height) { position.y = windowBounds.height - subMenu.height; if (position.y < 0) { position.y = 0; } } subMenu.add(position.x, position.y); this.addMenu(subMenu); }, /** * Handler for clicks on the overlay mask: de-activates menu. */ _processMaskClick: function(e) { this.deactivate(); return true; }, /** * Removes the menu mask. */ removeMask: function() { if (!this.maskDeployed) { return; } this._overlayRemove(); this.maskDeployed = false; Core.Web.Event.remove(document.body, "click", this._processMaskClickRef, false); Core.Web.Event.remove(document.body, "contextmenu", this._processMaskClickRef, false); }, renderAdd: function(update, parentElement) { this.menuModel = this.component.get("model"); this.stateModel = this.component.get("stateModel"); this.element = this.renderMain(update); this.element.tabIndex = "-1"; this.element.style.outlineStyle = "none"; parentElement.appendChild(this.element); }, renderDispose: function(update) { this.deactivate(); }, renderUpdate: function(update) { if (update.isUpdatedPropertySetIn({modal: true})) { // Do not re-render on update to modal state. return; } var element = this.element; var containerElement = element.parentNode; Echo.Render.renderComponentDispose(update, update.parent); containerElement.removeChild(element); this.renderAdd(update, containerElement); return false; }});Extras.Sync.Menu.RenderedMenu = Core.extend({ $static: { defaultIconTextMargin: 5, defaultMenuInsets: "2px", defaultMenuItemInsets: "1px 12px" }, menuSync: null, component: null, client: null, element: null, itemElements: null, menuModel: null, width: null, height: null, _activeItem: null, stateModel: null, $construct: function(menuSync, menuModel) { this.menuSync = menuSync; this.menuModel = menuModel; this.component = this.menuSync.component; this.client = this.menuSync.client; this.stateModel = this.menuSync.stateModel; this.itemElements = { }; }, add: function(x, y) { this.element.style.left = x + "px"; this.element.style.top = y + "px"; var animationTime = this.component.render("animationTime", 0); if (!animationTime || Core.Web.Env.NOT_SUPPORTED_CSS_OPACITY) { document.body.appendChild(this.element); } else { this.element.style.opacity = 0; var fadeRunnable = new Extras.Sync.FadeRunnable(this.element, true, 1, animationTime); Core.Web.Scheduler.add(fadeRunnable); document.body.appendChild(this.element); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -