Source: layout/AlfSideBarContainer.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 layout widget provides a resizeable sidebar that can be snapped open and closed into which widgets
 * can be placed. Each widget in the <b>widgets</b> array can given an optional <b>align</b> attribute that if set to
 * <b>"sidebar"</b> will result in that widget being placed into the sidebar (widgets without an <b>align</b> attribute
 * or with the <b>align</b> attribute set to any other value will be placed into the main panel).</p>
 * <p>If you don't want the sidebar to be resizeable then you can set the 
 * [isResizable]{@link module:alfresco/layout/AlfSideBarContainer#isResizable} to be false. This will result in a simple
 * border separating the sidebar and main areas.</p>
 *
 * @example <caption>Example configuration placing one widget in the sidebar and the other in the main panel</caption>
 * {
 *    name: "alfresco/layout/AlfSideBarContainer",
 *    config: {
 *       widgets: [
 *          {
 *             name: "alfresco/html/Label",
 *             align: "sidebar",
 *             config: {
 *                label: "This is in the sidebar"
 *             }
 *          },
 *          {
 *             name: "alfresco/html/Label",
 *             config: {
 *                label: "This is in the main panel"
 *             }
 *          }
 *       ]
 *    }
 * }
 *
 * @example <caption>Example configuration where the sidebar cannot be resized (with custom width)</caption>
 * {
 *    name: "alfresco/layout/AlfSideBarContainer",
 *    config: {
 *       isResizable: false,
 *       initialSidebarWidth: 250,
 *       widgets: [
 *          {
 *             name: "alfresco/html/Label",
 *             align: "sidebar",
 *             config: {
 *                label: "This is in the sidebar"
 *             }
 *          },
 *          {
 *             name: "alfresco/html/Label",
 *             config: {
 *                label: "This is in the main panel"
 *             }
 *          }
 *       ]
 *    }
 * }
 * 
 * @module alfresco/layout/AlfSideBarContainer
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/core/CoreWidgetProcessing
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "dijit/_TemplatedMixin",
        "alfresco/core/ResizeMixin",
        "alfresco/services/_PreferenceServiceTopicMixin",
        "dojo/text!./templates/AlfSideBarContainer.html",
        "alfresco/core/Core",
        "alfresco/core/CoreWidgetProcessing",
        "dijit/layout/BorderContainer",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/dom-style",
        "dojo/dom-class",
        "dojo/on",
        "dojo/dom-geometry",
        "dojo/window",
        "jquery",
        "jqueryui"], 
        function(declare, _WidgetBase, _TemplatedMixin, ResizeMixin, _PreferenceServiceTopicMixin, template, AlfCore, 
                 CoreWidgetProcessing, BorderContainer, lang, array, domStyle, domClass, on, domGeom, win, $) {
   
   return declare([_WidgetBase, _TemplatedMixin, ResizeMixin, _PreferenceServiceTopicMixin, AlfCore, CoreWidgetProcessing], {
      
      /**
       * An array of the CSS files to use with this widget.
       * 
       * @instance
       * @type {object[]}
       * @default [{cssFile:"./css/AlfSideBarContainer.css"}]
       */
      cssRequirements: [{cssFile:"/js/lib/jquery-ui-1.11.1/jquery-ui.css"},
                        {cssFile:"./css/AlfSideBarContainer.css"}],
      
      /**
       * The HTML template to use for the widget.
       * @instance
       * @type {string}
       */
      templateString: template,
      
      /**
       * It is possible to optionally provide an array of events that the widget should
       * subscribe to that trigger resize events. This has initially been added to address the problem that occurred when the
       * alfresco/wrapped/DocumentList widget would resize itself after the initial sizing causing the sidebar to render incorrectly.
       * By allowing custom events to be subscribed to it is possible to work around issues such as these.
       * 
       * @instance
       * @type {array} 
       * @default
       */
      customResizeTopics: null,
      
      /**
       * This property allows the height of the sidebar to accommodate a "sticky" footer. The height is otherwise calculated as
       * the height of the view port minus the top position of the side bar (unless either side bar or main content are larger).
       * By setting this property it is possible to also deduct the height of a sticky footer. 
       * 
       * @instance
       * @type {integer} 
       * @default
       */
      footerHeight: 0,
      
      /**
       * @instance
       * @type {integer}
       */
      hiddenSidebarWidth: null,
      
      /**
       * The initial width (in pixels) of the sidebar
       * @instance
       * @type {number} 
       * @default
       */
      initialSidebarWidth: 350,

      /**
       * Indicates whether or not the sidebar should be resizable or not.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.40
       */
      isResizable: true,
      
      /**
       * The last registered width (in pixels) of the sidebar (needed for window resize events)
       * @instance
       * @type {number}
       * @default
       */
      lastSidebarWidth: null,
      
      /**
       * The minimum width (in pixels) for the sidebar
       * @instance
       * @type {number} 
       * @default
       */
      minSidebarWidth: 150,
      
      /**
       * This will be set to the resize drag handle
       * 
       * @instance
       * @type {element}
       * @default
       */
      resizeHandlerNode: null,
      
      /**
       * Indicates whether or not to show the sidebar when initially rendered.
       * @instance
       * @type {boolean} 
       */
      showSidebar: true,
      
      /**
       * This is the dot-notation property to use in the user preferences object to retrieve and persist
       * the preferred width of the sidebar.
       * 
       * @instance
       * @type {string}
       * @default 
       */
      showSidebarPreferenceId: "org.alfresco.share.documentList.showSidebar",
      
      /**
       * This is the dot-notation property to use in the user preferences object to retrieve and persist
       * the preferred width of the sidebar.
       * 
       * @instance
       * @type {string}
       * @default 
       */
      sidebarWidthPreferenceId: "org.alfresco.share.sideBarWidth",

      /**
       * Makes a request to get the users sidebar width preference.
       * 
       * @instance
       */
      postMixInProperties: function alfresco_layout_AlfSideBarContainer__postMixInProperties() {
         this.alfPublish(this.getPreferenceTopic, {
            preference: this.sidebarWidthPreferenceId,
            callback: this.setSideBarWidth,
            callbackScope: this
         });
      },
      
      /**
       * Sets the initial sidebar width from the users saved preferences.
       * 
       * @instance
       * @param {number} value The saved width preference
       */
      setSideBarWidth: function alfresco_layout_AlfSideBarContainer__setSideBarWidth(value) {
         if (value || value === 0)
         {
            this.initialSidebarWidth = value;
         }
      },
      
      /**
       * Adds widgets to the sidebar and main container node and sets up the event handlers for
       * resize events.
       * 
       * @instance
       */
      postCreate: function alfresco_layout_AlfSideBarContainer__postCreate() {
         if (this.widgets)
         {
            array.forEach(this.widgets, lang.hitch(this, this.addWidget));
         }
         
         // Set up the resizer that allows the sidebar to be dynamically made larger or smaller...
         var size = parseInt(domStyle.get(this.domNode, "width"), 10);
         var max = (size - this.minSidebarWidth);
         if (max < this.minSidebarWidth)
         {
            max = null;
         }
         
         if (this.isResizable)
         {
            $(this.sidebarNode).resizable({
               handles: {
                  "e": ".resize-handle"
               },
               minWidth: this.minSidebarWidth,
               maxWidth: max,
               resize: lang.hitch(this, this.resizeHandler),
               stop: lang.hitch(this, this.endResizing)
            });

            on(this.resizeHandlerButtonNode, "click", lang.hitch(this, this.onResizeHandlerClick));
         }
         else
         {
            domClass.add(this.domNode, "alfresco-layout-AlfSideBarContainer--resize-disabled");
         }
         
         // We need to subscribe after the resize widget has been created...
         this.alfSubscribe("ALF_DOCLIST_SHOW_SIDEBAR", lang.hitch(this, this.showEventListener));

         // Subscribe to all the configured custom resize topics...
         if (this.customResizeTopics && this.customResizeTopics.length)
         {
            array.forEach(this.customResizeTopics, function(topic) {
               this.alfSubscribe(topic, lang.hitch(this, this.resizeHandler));
            }, this);
         }
         
         // Keep track of the overall browser window changing in size...
         this.alfSetupResizeSubscriptions(this.resizeHandler, this);
         this.addResizeListener(this.mainWidgets, this.domNode.parentNode);         
         this.addResizeListener(this.sidebarWidgets, this.domNode.parentNode);         
         
         // Perform the initial rendering...
         this.lastSidebarWidth = this.initialSidebarWidth;
         this.resizeHandler({width: this.lastSidebarWidth});
         this.render(this.showSidebar);
      },
      
      /**
       * 
       * @instance
       * @param {object} widget The widget to add
       * @param {integer} index The index of the widget
       */
      addWidget: function alfresco_layout_AlfSideBarContainer__addWidget(widget, /*jshint unused:false*/ index) {
         var domNode = null;
         if (widget.align === "sidebar")
         {
            domNode = this.createWidgetDomNode(widget, this.sidebarWidgets);
         }
         else
         {
            domNode = this.createWidgetDomNode(widget, this.mainWidgets);
         }
         this.createWidget(widget, domNode);
      },
      
      /**
       * 
       * @instance
       * @param {object} evt The resize event object
       * @param {object} ui The data about the resize
       */
      resizeHandler: function alfresco_layout_AlfSideBarContainer__resizeHandler(evt, ui) {

         // Adjust the widths
         var size = parseInt(domStyle.get(this.domNode, "width"), 10);
         var newWidth = this.lastSidebarWidth; // Initialise to last known width of the sidebar (needed for window resize events)
         var passedInWidth = lang.getObject("size.width", false, ui);
         if (passedInWidth || passedInWidth === 0)
         {
            newWidth = passedInWidth;
            this.lastSidebarWidth = newWidth;
         }
         domStyle.set(this.mainNode, "width", (size - newWidth - 16) + "px");
         if (!this.isResizable)
         {
            domStyle.set(this.sidebarNode, "width", newWidth + "px");
         }

         // Calculate the min-heights
         var scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop || 0,
            widgetRect = this.domNode.getBoundingClientRect(),
            offsetTop = widgetRect.top + scrollTop,
            minHeight = window.innerHeight - offsetTop - this.footerHeight,
            newHeight = Math.max(minHeight, this.sidebarWidgets.offsetHeight, this.mainWidgets.offsetHeight),
            nodes = [this.sidebarNode, this.mainNode];
         array.forEach(nodes, function(node) {
            domStyle.set(node, "minHeight", newHeight + "px");
         });
         
         // Fire a custom event to let contained objects know that the node has been resized.
         this.alfPublishResizeEvent(this.mainNode);
         this.alfPublishResizeEvent(this.sidebarNode);
      },
      
      /**
       * Calls [resizeHandler]{@link module:alfresco/layout/AlfSideBarContainer#resizeHandler} and then
       * saves the new width as a user preference.
       * 
       * @instance
       * @param {object} evt The resize event
       */
      endResizing: function alfresco_layout_AlfSideBarContainer__endResizing(evt, ui) {
         this.resizeHandler(evt, ui);
         this.alfPublish(this.setPreferenceTopic, {
            preference: this.sidebarWidthPreferenceId,
            value: this.lastSidebarWidth
         }, true);
         this.hiddenSidebarWidth = this.lastSidebarWidth;
      },
      
      /**
       * 
       * @instance
       * @param {object} payload The payload published on the subscribed topic
       */
      showEventListener: function alfresco_layout_AlfSidebarContainer__showEventListener(payload) {
         this.alfLog("log", "Handle show request", payload);
         if (payload)
         {
            this.render(payload.selected);
            this.alfPublish(this.setPreferenceTopic, {
               preference: this.showSidebarPreferenceId,
               value: payload.selected
            }, true);
         }
      },
      
      /**
       * Handles a user explicitly clicking on the resize handle node to toggle the sidebar being shown
       * 
       * @instance
       * @param {object} evt The click event
       */
      onResizeHandlerClick: function alfresco_layout_AlfSidebarContainer__onResizeHandlerClick(/*jshint unused:false*/ evt) {
         if (this.resizeHandlerButtonNode)
         {
            // Render the sidebar depending upon whether or not the sidebar is currently being shown or
            // not. We're using the classes applied to the drag handle node to determine whether or not
            // to show or hide the sidebar.
            this.alfPublish("ALF_DOCLIST_SHOW_SIDEBAR", {
               selected: domClass.contains(this.sidebarNode, "alfresco-layout-AlfSideBarContainer__sidebar--closed")
            });
         }
      },
      
      /**
       * Renders the sidebar container (basically controls whether or not the side bar is displayed or not).
       * 
       * @instance
       * @param {boolean} show Indicates whether or not to show the sidebar
       */
      render: function alfresco_layout_AlfSidebarContainer__showSidebar(show) {
         if (show)
         {
            // Hide all the child nodes of the side bar (except for the resize handle)...
            for (var i=0; i<this.sidebarNode.children.length - 1; i++)
            {
               if (this.sidebarNode.children[i] !== this.resizeHandlerNode)
               {
                  domClass.remove(this.sidebarNode.children[i], "share-hidden");
               }
            }
            
            // Show the sidebar...
            if (this.isResizable)
            {
               $(this.sidebarNode).resizable("enable"); // Unlock the resizer when the sidebar is not shown...
            }
            
            var width = (this.hiddenSidebarWidth) ? this.hiddenSidebarWidth : this.initialSidebarWidth;
            domStyle.set(this.sidebarNode, "width", width + "px");

            this.lastSidebarWidth = width;
            this.resizeHandler();

            domClass.remove(this.sidebarNode, "alfresco-layout-AlfSideBarContainer__sidebar--closed");
         }
         else
         {
            // Hide the sidebar...
            domStyle.set(this.sidebarNode, "width", "9px");

            this.lastSidebarWidth = 9;
            this.resizeHandler();
            $(this.sidebarNode).resizable("disable"); // Lock the resizer when the sidebar is not shown...

            domClass.add(this.sidebarNode, "alfresco-layout-AlfSideBarContainer__sidebar--closed");
         }
      }
   });
});