/** * EGroupware eTemplate2 - JS Dropdown Button object * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @link https://www.egroupware.org * @author Nathan Gray * @copyright Nathan Gray 2013 */ /*egw:uses /vendor/bower-asset/jquery/dist/jquery.js; /vendor/bower-asset/jquery-ui/jquery-ui.js; et2_baseWidget; */ import {et2_inputWidget} from './et2_core_inputWidget'; import {WidgetConfig, et2_register_widget} from "./et2_core_widget"; import {ClassWithAttributes} from "./et2_core_inheritance"; import {et2_no_init} from "./et2_core_common"; import {egw} from "../jsapi/egw_global"; /** * A split button - a button with a dropdown list * * There are several parts to the button UI: * - Container: This is what is percieved as the dropdown button, the whole package together * - Button: The part on the left that can be clicked * - Arrow: The button to display the choices * - Menu: The list of choices * * Menu options are passed via the select_options. They are normally ID => Title pairs, * as for a select box, but the title can also be full HTML if needed. * * @augments et2_inputWidget */ export class et2_dropdown_button extends et2_inputWidget { static readonly attributes : any = { "label": { "name": "caption", "type": "string", "description": "Label of the button", "translate": true, "default": "Select..." }, "label_updates": { "name": "Label updates", "type": "boolean", "description": "Button label updates when an option is selected from the menu", "default": true }, "image": { "name": "Icon", "type": "string", "description": "Add an icon" }, "ro_image": { "name": "Read-only Icon", "type": "string", "description": "Use this icon instead of hiding for read-only" }, "onclick": { "description": "JS code which gets executed when the button is clicked" }, "select_options": { "type": "any", "name": "Select options", "default": {}, "description": "Select options for dropdown. Can be a simple key => value list, or value can be full HTML", // Skip normal initialization for this one "ignore": true }, "accesskey": { "name": "Access Key", "type": "string", "default": et2_no_init, "description": "Alt + activates widget" }, "tabindex": { "name": "Tab index", "type": "integer", "default": et2_no_init, "description": "Specifies the tab order of a widget when the 'tab' button is used for navigating." }, // No such thing as a required button "required": { "ignore": true } }; internal_ids : any = { div: "", button: "", menu: "" }; div : JQuery = null; buttons : JQuery = null; button : JQuery = null; arrow : JQuery = null; menu : JQuery = null; image : JQuery = null; clicked : boolean = false; label_updates : boolean = true; value : any = null; /** * Default menu, so there is something for the widget browser / editor to show */ readonly default_menu : string = ''; /** * Constructor * * @memberOf et2_dropdown_button */ constructor(_parent?, _attrs? : WidgetConfig, _child? : object) { super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_dropdown_button._attributes, _child || {})); this.clicked = false; let self = this; // Create the individual UI elements // Menu is a UL this.menu = jQuery(this.default_menu).attr("id",this.internal_ids.menu) .hide() .menu({ select: function(event,ui) { self.onselect.call(self,event,ui.item); } }); this.buttons = jQuery(document.createElement("div")) .addClass("et2_dropdown"); // Main "wrapper" div this.div = jQuery(document.createElement("div")) .attr("id", this.internal_ids.div) .append(this.buttons) .append(this.menu); // Left side - activates click action this.button = jQuery(document.createElement("button")) .attr("id", this.internal_ids.button) .attr("type", "button") .addClass("ui-widget ui-corner-left").removeClass("ui-corner-all") .appendTo(this.buttons); // Right side - shows dropdown this.arrow = jQuery(document.createElement("button")) .addClass("ui-widget ui-corner-right").removeClass("ui-corner-all") .attr("type", "button") .click(function() { // ignore click on readonly button if (self.options.readonly) return false; // Clicking it again hides menu if(self.menu.is(":visible")) { self.menu.hide(); return false; } // Show menu dropdown var menu = self.menu.show().position({ my: "left top", at: "left bottom", of: self.buttons }); // Hide menu if clicked elsewhere jQuery( document ).one( "click", function() { menu.hide(); }); return false; }) // This is the actual down arrow icon .append("
") .appendTo(this.buttons); // Common button UI this.buttons.children("button") .addClass("ui-state-default") .hover( function() {jQuery(this).addClass("ui-state-hover");}, function() {jQuery(this).removeClass("ui-state-hover");} ); // Icon this.image = jQuery(document.createElement("img")); this.setDOMNode(this.div[0]); } destroy() { // Destroy widget if(this.menu && this.menu.data('ui-menu')) this.menu.menu("destroy"); // Null children this.image = null; this.button = null; this.arrow = null; this.buttons = null; this.menu = null; // Remove this.div.empty().remove(); } set_id(_id) { super.set_id(_id); // Update internal IDs - not really needed since we refer by internal // javascript reference, but good to keep up to date this.internal_ids = { div: this.dom_id + "_wrapper", button: this.dom_id, menu: this.dom_id + "_menu" }; for(let key in this.internal_ids) { if(this[key] == null) continue; this[key].attr("id", this.internal_ids[key]); } } /** * Set if the button label changes to match the selected option * * @param updates boolean Turn updating on or off */ set_label_updates(updates) { this.label_updates = updates; } set_accesskey(key) { jQuery(this.node).attr("accesskey", key); } set_ro_image(_image) { if(this.options.readonly) { this.set_image(_image); } } set_image(_image) { if(!this.isInTree() || this.image == null) return; if(!_image.trim()) { this.image.hide(); } else { this.image.show(); } let src = this.egw().image(_image); if(src) { this.image.attr("src", src); } // allow url's too else if (_image[0] == '/' || _image.substr(0,4) == 'http') { this.image.attr('src', _image); } else { this.image.hide(); } } /** * Overwritten to maintain an internal clicked attribute * * @param _ev * @returns {Boolean} */ click(_ev) { // ignore click on readonly button if (this.options.readonly) return false; this.clicked = true; if (!super.click(_ev)) { this.clicked = false; return false; } this.clicked = false; return true; } onselect(event, selected_node) { this.set_value(selected_node.attr("data-id")); this.change(selected_node); } attachToDOM() { let res = super.attachToDOM(); // Move the parent's handler to the button, or we can't tell the difference between the clicks jQuery(this.node).unbind("click.et2_baseWidget"); this.button.off().bind("click.et2_baseWidget", this, function(e) { return e.data.click.call(e.data, this); }); return res; } set_label(_value) { if (this.button) { this.label = _value; this.button.text(_value) .prepend(this.image); } } /** * Set the options for the dropdown * * @param options Object ID => Label pairs */ set_select_options(options) { this.menu.first().empty(); // Allow more complicated content, if passed if(typeof options == "string") { this.menu.append(options); } else { let add_complex = function(node, options) { for(let key in options) { let item; if(typeof options[key] == "string") { item = jQuery("
  • "+options[key]+"
  • "); } else if (options[key]["label"]) { item =jQuery("
  • "+options[key]["label"]+"
  • "); } // Optgroup else { item = jQuery("
  • "+key+"
  • "); add_complex(node.append("