/
githubmirror
/
brackets
Обзор
Документация
Войти
/
githubmirror
/
brackets
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
release-1.11
src/widgets/DropdownButton.js
352 строки
13 KB
Marcel Gerber
Merge pull request #12671 from ficristo/fix-filter-pencil-pos
14 авг 2016, 20:32
14 авг 2016, 20:32
eba3d47
Код
Авторство
О чём код?
/* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * Button that opens a dropdown list when clicked. More akin to a popup menu than a combobox. Compared to a * simple <select> element: * - There's no "selected" state * - The button's label is not automatically changed when an item in the list is clicked * - Its width is not the max of all the dropdown items' labels * - The button & dropdown's appearance can be customized far more * Events * - listRendered -- This event is dispatched after the entire list is rendered so that custom event handlers can be * set up for any custom UI in the list. * * TODO: merge DropdownEventHandler into this? Are there any other widgets that might want to use it separately? * */ define(function (require, exports, module) { "use strict"; // Load dependent modules var DropdownEventHandler = require("utils/DropdownEventHandler").DropdownEventHandler, EventDispatcher = require("utils/EventDispatcher"), WorkspaceManager = require("view/WorkspaceManager"), Menus = require("command/Menus"), ViewUtils = require("utils/ViewUtils"), _ = require("thirdparty/lodash"); /** * Creates a single dropdown-button instance. The DOM node is created but not attached to * the document anywhere - clients should append this.$button to the appropriate location. * * DropdownButton dispatches the following events: * - "select" - when an option in the dropdown is clicked. Passed item object and index. * * @param {!string} label Label to display on the button * @param {!Array.<*>} items Items in the dropdown list. It generally doesn't matter what type/value the * items have, except that any item === "---" will be treated as a divider. Such items are not * clickable and itemRenderer() will not be called for them. * @param {?function(*, number):!string|{html:string, enabled:boolean} itemRenderer Optional function to * convert a single item to HTML (see itemRenderer() docs below). If not provided, items are * assumed to be plain text strings. */ function DropdownButton(label, items, itemRenderer) { this.items = items; this.itemRenderer = itemRenderer || this.itemRenderer; this._onClick = this._onClick.bind(this); this.closeDropdown = this.closeDropdown.bind(this); this._onClickOutside = this._onClickOutside.bind(this); this.$button = $("<button class='btn btn-dropdown'/>") .text(label) .on("click", this._onClick); } EventDispatcher.makeEventDispatcher(DropdownButton.prototype); /** * Items in dropdown list - may be changed any time dropdown isn't open * @type {!Array.<*>} */ DropdownButton.prototype.items = null; /** * The clickable button. Available as soon as the DropdownButton is constructed. * @type {!jQueryObject} */ DropdownButton.prototype.$button = null; /** * The dropdown element. Only non-null while open. * @type {?jQueryObject} */ DropdownButton.prototype.$dropdown = null; /** * Extra CSS class(es) to apply to $dropdown * @type {?string} */ DropdownButton.prototype.dropdownExtraClasses = null; /** * @private * Where to restore focus when dropdown closed * @type {?HTMLElement} */ DropdownButton.prototype._lastFocus = null; /** * @private * Helper object for dropdown. Only non-null while open. * @type {?DropdownEventHandler} */ DropdownButton.prototype._dropdownEventHandler = null; /** * @private * Handle clicking button */ DropdownButton.prototype._onClick = function (event) { if (!this.$button.hasClass("disabled")) { this.toggleDropdown(); } // Indicate click was handled (e.g. to shield from MultiRangeInlineEditor._onClick()) event.stopPropagation(); }; /** * Update the button label. * @param {string} label */ DropdownButton.prototype.setButtonLabel = function (label) { if (!this.$button) { return; } $(this.$button).text(label); }; /** * Called for each item when rendering the dropdown. * @param {*} item from items array * @param {number} index in items array * @return {!string|{html:string, enabled:boolean}} Formatted & escaped HTML, either as a simple string * or as the 'html' field in an object that also conveys enabled state. Disabled items inherit gray * text color and cannot be selected. */ DropdownButton.prototype.itemRenderer = function (item, index) { return _.escape(String(item)); }; /** * Converts the list of item objects into HTML list items in format required by DropdownEventHandler * @param {!jQueryObject} parent The dropdown element * @return {!jQueryObject} The dropdown element with the rendered list items appended. */ DropdownButton.prototype._renderList = function (parent) { if (!parent) { return null; } var html = ""; this.items.forEach(function (item, i) { if (item === "---") { html += "<li class='divider'></li>"; } else { var rendered = this.itemRenderer(item, i), itemHtml = rendered.html || rendered, disabledClass = (rendered.html && !rendered.enabled) ? "disabled" : ""; html += "<li><a class='stylesheet-link " + disabledClass + "' data-index='" + i + "'>"; html += itemHtml; html += "</a></li>"; } }.bind(this)); parent.append(html); // Also trigger listRendered handler so that custom event handlers can be // set up for any custom UI in the list. this.trigger("listRendered", parent); // Also need to re-register mouse event handlers with the updated list. if (this._dropdownEventHandler) { this._dropdownEventHandler.reRegisterMouseHandlers(parent); } return parent; }; /** * Refresh the dropdown list by removing and re-creating all list items. * Call this after deleting/adding any item in the dropdown list. */ DropdownButton.prototype.refresh = function () { if (!this.$dropdown) { return; } // Remove all list items and then re-create them from this.items. $("li", this.$dropdown).remove(); this._renderList(this.$dropdown); }; /** * Check/Uncheck the list item of the given index. * @param {number} index The index of the list item to be checked or unchecked * @param {boolean} checked True if the list item is to be checked, false to get check * mark removed. */ DropdownButton.prototype.setChecked = function (index, checked) { if (!this.$dropdown) { return; } var listItems = $("li", this.$dropdown), count = listItems.length; if (index > -1 && index < count) { $("a", listItems[index]).toggleClass("checked", checked); } }; /** Pops open the dropdown if currently closed. Does nothing if items.length == 0 */ DropdownButton.prototype.showDropdown = function () { // Act like a plain old button if no items to show if (!this.items.length) { return; } if (this.$dropdown) { return; } Menus.closeAll(); var $dropdown = $("<ul class='dropdown-menu dropdownbutton-popup' tabindex='-1'>") .addClass(this.dropdownExtraClasses) // (no-op if unspecified) .css("min-width", this.$button.outerWidth()); // do this before the clipping calcs below this.$dropdown = $dropdown; this._renderList(this.$dropdown) .appendTo($("body")) .data("attached-to", this.$button[0]); // keep ModalBar open while dropdown focused // Calculate position of dropdown var toggleOffset = this.$button.offset(), posLeft = toggleOffset.left, posTop = toggleOffset.top + this.$button.outerHeight(), elementRect = { top: posTop, left: posLeft, height: $dropdown.height(), width: $dropdown.width() }, clip = ViewUtils.getElementClipSize($(window), elementRect); if (clip.bottom > 0) { // Bottom is clipped, so move entire menu above button posTop = Math.max(0, toggleOffset.top - $dropdown.height() - 4); } // Take in consideration the scrollbar to prevent unexpected behaviours (see #10963). var dropdownElement = this.$dropdown[0]; var scrollWidth = dropdownElement.offsetWidth - dropdownElement.clientWidth + 1; if (clip.right > 0) { // Right is clipped, so adjust left to fit menu in editor. posLeft = Math.max(0, posLeft - clip.right - scrollWidth); } $dropdown.css({ left: posLeft, top: posTop, width: $dropdown.width() + scrollWidth }); // Attach event handlers this._dropdownEventHandler = new DropdownEventHandler($dropdown, this._onSelect.bind(this), this._onDropdownClose.bind(this)); this._dropdownEventHandler.open(); window.document.body.addEventListener("mousedown", this._onClickOutside, true); WorkspaceManager.on("workspaceUpdateLayout", this.closeDropdown); // Manage focus this._lastFocus = window.document.activeElement; $dropdown.focus(); }; /** * @private * Clean up event handlers after dropdown closed & dispose old dropdown DOM. Called regardless of how the dropdown * was closed. */ DropdownButton.prototype._onDropdownClose = function () { window.document.body.removeEventListener("mousedown", this._onClickOutside, true); WorkspaceManager.off("workspaceUpdateLayout", this.closeDropdown); // Restore focus to old pos, unless "select" handler changed it if (window.document.activeElement === this.$dropdown[0]) { this._lastFocus.focus(); } this._dropdownEventHandler = null; this.$dropdown = null; // already remvoed from DOM automatically by PopUpManager }; /** Closes the dropdown if currently open */ DropdownButton.prototype.closeDropdown = function () { if (this._dropdownEventHandler) { this._dropdownEventHandler.close(); } }; /** * @private * Clicking outside the dropdown closes it */ DropdownButton.prototype._onClickOutside = function (event) { var $container = $(event.target).closest(".dropdownbutton-popup"); // If click is outside dropdown list or dropdown button, then close dropdown list if (!$(event.target).is(this.$button) && ($container.length === 0 || $container[0] !== this.$dropdown[0])) { this.closeDropdown(); event.stopPropagation(); event.preventDefault(); } }; /** Opens the dropdown if closed; closes it if open */ DropdownButton.prototype.toggleDropdown = function () { if (this.$dropdown) { this.closeDropdown(); } else { this.showDropdown(); } }; /** * @private * Callback from DropdownEventHandler when item in dropdown list is selected (via mouse or keyboard) * @param {!jQueryObject} $link The `a` element selected */ DropdownButton.prototype._onSelect = function ($link) { var itemIndex = Number($link.data("index")); this.trigger("select", this.items[itemIndex], itemIndex); }; exports.DropdownButton = DropdownButton; });