Source: misc/AlfTooltip.js

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * <p>This widget provides a way in which a widget model can be displayed when the mouse moves over another
 * widget. To use this tooltip you need to define a 
 * [widgets model to immediately render]{@link module:alfresco/misc/AlfTooltipDialog#widgets} and then another
 * [widget model]{@link module:alfresco/misc/AlfTooltipDialog#widgetsForTooltip} that will be displayed in the
 * tooltip when the mouse moves over any of the widgets in the first model.</p>
 * 
 * @example <caption>Example configuration:</caption>
 * {
 *    name: "alfresco/layout/ClassicWindow",
 *    config: {
 *    title: "Tooltip displays on mouse over logo",
 *    widgets: [
 *       {
 *          name: "alfresco/misc/AlfTooltip",
 *          config: {
 *             widgets: [
 *                {
 *                   name: "alfresco/logo/Logo"
 *                }
 *             ],
 *             widgetsForTooltip: [
 *                {
 *                   name: "alfresco/html/Label",
 *                   config: {
 *                      label: "This is the tooltip content"
 *                   }
 *                }
 *             ]
 *          }
 *       }
 *    ]
 * }
 * 
 * @example <caption>Example configuration with longer delay before displaying:</caption>
 * {
 *    name: "alfresco/layout/ClassicWindow",
 *    config: {
 *    title: "Tooltip displays on mouse over logo",
 *    widgets: [
 *       {
 *          name: "alfresco/misc/AlfTooltip",
 *          config: {
 *             mouseoverShowDelay: 1000,
 *             widgets: [
 *                {
 *                   name: "alfresco/logo/Logo"
 *                }
 *             ],
 *             widgetsForTooltip: [
 *                {
 *                   name: "alfresco/html/Label",
 *                   config: {
 *                      label: "This is the tooltip content"
 *                   }
 *                }
 *             ]
 *          }
 *       }
 *    ]
 * }
 * 
 * @example <caption>Example configuration using a click event rather than hover and width styling:</caption>
 * {
 *    name: "alfresco/layout/ClassicWindow",
 *    config: {
 *    title: "Tooltip displays on mouse over logo",
 *    widgets: [
 *       {
 *          name: "alfresco/misc/AlfTooltip",
 *          config: {
 *             widgets: [
 *                {
 *                   name: "alfresco/logo/Logo"
 *                }
 *             ],
 *             widgetsForTooltip: [
 *                {
 *                   name: "alfresco/html/Label",
 *                   config: {
 *                      label: "This is the tooltip content"
 *                   }
 *                }
 *             ],
 *             triggeringEvent: "click",
 *             tooltipStyle: "width: 350px;"
 *          }
 *       }
 *    ]
 * }
 * 
 * @module alfresco/misc/AlfTooltip
 * @extends external:dijit/Menu
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/core/CoreWidgetProcessing
 * @author Dave Draper
 * @author Martin Doyle
 */
define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "dijit/_TemplatedMixin",
        "dojo/text!./templates/AlfTooltip.html",
        "dijit/TooltipDialog",
        "alfresco/core/Core",
        "alfresco/core/CoreWidgetProcessing",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/dom-construct",
        "dojo/dom-style",
        "dojo/on",
        "dojo/query",
        "dojo/when",
        "dijit/popup"], 
        function(declare, _WidgetBase, _TemplatedMixin, template, TooltipDialog, AlfCore, CoreWidgetProcessing,
                 lang, array, domConstruct, domStyle, on, query, when, popup) {
   
   return declare([_WidgetBase, _TemplatedMixin, AlfCore, CoreWidgetProcessing], {
      
      /**
       * The HTML template to use for the widget.
       * @instance
       * @type {String}
       */
      templateString: template,
      
      /**
       * <p>This is an optional CSS selector that can be provided to identify a child node that the tooltip
       * should be anchored to. Ideally this should only match a single node, but if it matches multiple
       * nodes then the first result will be used. This selector is only used within the DOM that descends
       * from this widget - not across the entire page.</p>
       * <p>PLEASE NOTE: Use of this attribute is potentially fragile depending
       * upon the selector used - there are no guarantees that the CSS classes or HTML structure of widgets
       * will not change between releases of Aikau! It is strongly recommended that if future proof selectors
       * are required that feature requests are made to add them.</p>
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.71
       */
      anchorSelector: null,

      /**
       * An optional array of the orientation preferences for the tooltip. An example configuration might
       * be ["below-centered", "above-centered"] for example.
       * 
       * @instance
       * @type {string[]}
       * @default
       * @since 1.0.71
       */
      orientation: null,

      /**
       * This is the widget model that will be displayed inside the tooltip.
       * 
       * @instance
       * @type {array}
       * @default
       */
      widgetsForTooltip: null,

      /**
       * This is the widget model that will be immediately rendered. When the mouse is moved over any
       * of the widgets in this model then the tooltip will be created (if it hasn't previously been created)
       * and will then be displayed.
       * 
       * @instance
       * @type {array}
       * @default
       */
      widgets: null,

      /**
       * A reference to the dijit/TooltipDialog that will be created.
       *
       * @instance
       * @type {object}
       * @default
       */
      _tooltip: null,

      /**
       * The style to be applied to the tooltip. Default is null.
       *
       * @instance
       * @type {string}
       * @default
       */
      tooltipStyle: null,

      /**
       * The Javascript "dojo/on" event to listen for. Default is mouseover.
       *
       * @instance
       * @type {string}
       * @default
       */
      triggeringEvent: "mouseover",

      /**
       * How long (ms) to delay displaying the tooltip on mouseover-triggered tooltips.
       *
       * @instance
       * @type {number}
       * @default
       */
      mouseoverShowDelay: 250,

      /**
       * How long (ms) to delay hiding the tooltip on mouseover-triggered tooltips.
       *
       * @instance
       * @type {number}
       * @default
       */
      mouseoutHideDelay: 250,

      /**
       * The pointer for the timeout that will display a tooltip
       *
       * @instance
       * @type {number}
       * @default
       */
      _showTooltipTimeout: 0,

      /**
       * The pointer for the timeout that will hide a tooltip
       *
       * @instance
       * @type {number}
       * @default
       */
      _hideTooltipTimeout: 0,

      /**
       * This is called to display the tooltip. If the tooltip hasn't been created at this point then it will be created.
       * 
       * @instance
       */
      showTooltip: function alfresco_misc_AlfTooltip__showTooltip() {
         if (!this._tooltip)
         {
            this.dialogContent = domConstruct.create("div");
            this.processWidgets(this.widgetsForTooltip, this.dialogContent);
            this._tooltip = new TooltipDialog({
               id: this.id + "_TOOLTIP",
               style: this.tooltipStyle,
               content: this.dialogContent,
               onMouseEnter: lang.hitch(this, this.onTooltipMouseEnter),
               onMouseLeave: lang.hitch(this, this.onTooltipMouseLeave)
            });
         }

         // If a CSS selector has been provided for finding the element to anchor to, then
         // use it to set the target node...
         var targetNode = this.domNode;
         if (this.anchorSelector)
         {
            var results = query(this.anchorSelector, this.domNode);
            if (results.length)
            {
               targetNode = results[0];
            }
         }

         popup.open({
            popup: this._tooltip,
            around: targetNode,
            orient: this.orientation
         });
      },

      /**
       * Called to hide the tooltip. This is done when the mouse leaves the target area by default.
       * 
       * @instance
       */
      hideTooltip: function alfresco_misc_AlfTooltip__hideTooltip() {
         popup.close(this._tooltip);
      },

      /**
       * Sets up the mouse over listener for displaying the tooltip (if
       * [widgetsForTooltip]{@link module:alfresco/misc/AlfTooltipDialog#widgetsForTooltip} contains a widget
       * model) and then processes [widgets]{@link module:alfresco/misc/AlfTooltipDialog#widgets}.
       * 
       * @instance
       */
      postCreate: function alfresco_misc_AlfTooltip__postCreate() {
         if (this.widgetsForTooltip)
         {
            if (this.triggeringEvent === "mouseover") {
               on(this.domNode, "mouseover", lang.hitch(this, this.onMouseover));
               on(this.domNode, "mouseout", lang.hitch(this, this.onMouseout));
            } else {
               on(this.domNode, this.triggeringEvent, lang.hitch(this, this.showTooltip));
            }
         }
         else
         {
            this.alfLog("warn", "A tooltip dialog was configured without a 'widgetsForTooltip' attribute", this);
         }

         if (this.widgets)
         {
            this.processWidgets(this.widgets, this.domNode);
         }
      },

      /**
       * Handle mousing-over the widget.
       *
       * @instance
       */
      onMouseover: function alfresco_misc_AlfTooltip__onMouseover() {
         clearTimeout(this._hideTooltipTimeout);
         this._showTooltipTimeout = setTimeout(lang.hitch(this, this.showTooltip), this.mouseoverShowDelay);
      },

      /**
       * Handle mousing-out of the widget.
       *
       * @instance
       */
      onMouseout: function alfresco_misc_AlfTooltip__onMouseout() {
         clearTimeout(this._showTooltipTimeout);
         this._hideTooltipTimeout = setTimeout(lang.hitch(this, this.hideTooltip), this.mouseoutHideDelay);
      },

      /**
       * Handle mousing-over the tooltip.
       *
       * @instance
       */
      onTooltipMouseEnter: function alfresco_misc_AlfTooltip__onTooltipMouseEnter() {
         clearTimeout(this._hideTooltipTimeout);
      },

      /**
       * Handle mousing-out of the tooltip.
       *
       * @instance
       */
      onTooltipMouseLeave: function alfresco_misc_AlfTooltip__onTooltipMouseLeave() {
         this._hideTooltipTimeout = setTimeout(lang.hitch(this, this.hideTooltip), this.mouseoutHideDelay);
      },

      /**
       * The tooltip needs to take responsibility for delegating any resize requests that its child widgets
       * would usually get if they were not contained within the tooltip. This is particularly important
       * when widgets are placed in a [grid]{@link module:alfresco/lists/views/layouts/Grid} that will request
       * to resize those widgets. Without this delegation the widgets intended to be resized will stay their
       * original size. If the widget does not have a resize function then its DOM node will be resized.
       * If the widget has mixed in the [ResizeMixin]{@link module:alfresco/core/ResizeMixin} then its
       * [alfPublishResizeEvent]{@link module:alfresco/core/ResizeMixin#alfPublishResizeEvent} will be called.
       * 
       * @instance
       * @param {object} dimensions The object containing the width and height for the widget.
       * @since 1.0.47
       */
      resize: function alfresco_misc_AlfTooltip__resize(dimensions) {
         when(this.getProcessedWidgets(), lang.hitch(this, function(widgets) {
            array.forEach(widgets, function(widget) {
               if (widget && typeof widget.resize === "function")
               {
                  widget.resize(dimensions);
               }
               else
               {
                  domStyle.set(widget.domNode, "width", dimensions.w);
                  if (typeof widget.alfPublishResizeEvent === "function")
                  {
                     this.alfPublishResizeEvent(widget.domNode);
                  }
               }
            });
         }));
      }
   });
});