+++ /dev/null
-/*\r
-Copyright 2007 - 2008 University of Toronto\r
-Copyright 2007 University of Cambridge\r
-\r
-Licensed under the Educational Community License (ECL), Version 2.0 or the New\r
-BSD license. You may not use this file except in compliance with one these\r
-Licenses.\r
-\r
-You may obtain a copy of the ECL 2.0 License and BSD License at\r
-https://source.fluidproject.org/svn/LICENSE.txt\r
-*/\r
-\r
-// Declare dependencies.\r
-var fluid = fluid || {};\r
-\r
-fluid.Reorderer = function (container, params) {\r
- // Reliable 'this'.\r
- //\r
- var thisReorderer = this;\r
- var theAvatar = null;\r
- \r
- this.domNode = jQuery (container);\r
- \r
- // the reorderable DOM element that is currently active\r
- this.activeItem = null;\r
- \r
- this.layoutHandler = null;\r
- \r
- this.messageNamebase = "message-bundle:";\r
- \r
- this.orderableFinder = null;\r
- \r
- this.cssClasses = {\r
- defaultStyle: "orderable-default",\r
- selected: "orderable-selected",\r
- dragging: "orderable-dragging",\r
- hover: "orderable-hover",\r
- focusTarget: "orderable-focus-target",\r
- dropMarker: "orderable-drop-marker",\r
- avatar: "orderable-avatar"\r
- };\r
-\r
- if (params) {\r
- fluid.mixin (this, params);\r
- }\r
- \r
- /**\r
- * Return the element within the item that should receive focus. This is determined by \r
- * cssClasses.focusTarget. If it is not specified, the item itself is returned.\r
- * \r
- * @param {Object} item\r
- * @return {Object} The element that should receive focus in the specified item.\r
- */\r
- this.findElementToFocus = function (item) {\r
- var elementToFocus = jQuery ("." + this.cssClasses.focusTarget, item).get (0);\r
- if (elementToFocus) {\r
- return elementToFocus;\r
- }\r
- return item;\r
- };\r
- \r
- function setupDomNode (domNode) {\r
- domNode.focus(thisReorderer.handleFocus);\r
- domNode.blur (thisReorderer.handleBlur);\r
- domNode.keydown (thisReorderer.handleKeyDown);\r
- domNode.keyup (thisReorderer.handleKeyUp);\r
- domNode.removeAttr ("aaa:activedescendent");\r
- } \r
- \r
- /**\r
- * Changes the current focus to the specified item.\r
- * @param {Object} anItem\r
- */\r
- this.focusItem = function(anItem) {\r
- if (this.activeItem !== anItem) {\r
- this.changeActiveItemToDefaultState();\r
- this._setActiveItem (anItem); \r
- }\r
- \r
- var jActiveItem = jQuery (this.activeItem);\r
- jActiveItem.removeClass (this.cssClasses.defaultStyle);\r
- jActiveItem.addClass (this.cssClasses.selected);\r
- \r
- this.addFocusToElement (this.findElementToFocus (this.activeItem));\r
- };\r
- \r
- this.addFocusToElement = function (anElement) {\r
- var jElement = jQuery (anElement);\r
- if (!jElement.hasTabIndex ()) {\r
- jElement.tabIndex (-1);\r
- }\r
- jElement.focus ();\r
- };\r
- \r
- this.removeFocusFromElement = function (anElement) {\r
- jQuery (anElement).blur ();\r
- };\r
-\r
- /**\r
- * Changes focus to the active item.\r
- */\r
- this.selectActiveItem = function() {\r
- if (!this.activeItem) {\r
- var orderables = this.orderableFinder (this.domNode.get(0));\r
- if (orderables.length > 0) {\r
- this._setActiveItem (orderables[0]);\r
- }\r
- else {\r
- return;\r
- }\r
- }\r
- this.focusItem (this.activeItem);\r
- };\r
- \r
- this.handleFocus = function (evt) {\r
- thisReorderer.selectActiveItem();\r
- return false;\r
- };\r
- \r
- this.handleBlur = function (evt) {\r
- // Temporarily disabled blur handling in IE. See FLUID-7 for details.\r
- if (!jQuery.browser.msie) {\r
- thisReorderer.changeActiveItemToDefaultState();\r
- }\r
- };\r
- \r
- this.changeActiveItemToDefaultState = function() {\r
- if (this.activeItem) {\r
- var jActiveItem = jQuery (this.activeItem);\r
- jActiveItem.removeClass (this.cssClasses.selected);\r
- jActiveItem.addClass (this.cssClasses.defaultStyle);\r
- this.removeFocusFromElement (jActiveItem);\r
- }\r
- };\r
- \r
- this.handleKeyDown = function (evt) {\r
- if (thisReorderer.activeItem && evt.keyCode === fluid.keys.CTRL) {\r
- jQuery (thisReorderer.activeItem).removeClass (thisReorderer.cssClasses.selected);\r
- jQuery (thisReorderer.activeItem).addClass (thisReorderer.cssClasses.dragging);\r
- thisReorderer.activeItem.setAttribute ("aaa:grab", "true");\r
- return false;\r
- }\r
-\r
- return thisReorderer.handleArrowKeyPress(evt);\r
- };\r
-\r
- this.handleKeyUp = function (evt) {\r
- if (thisReorderer.activeItem && evt.keyCode === fluid.keys.CTRL) {\r
- jQuery (thisReorderer.activeItem).removeClass (thisReorderer.cssClasses.dragging);\r
- jQuery (thisReorderer.activeItem).addClass (thisReorderer.cssClasses.selected);\r
- thisReorderer.activeItem.setAttribute ("aaa:grab", "supported");\r
- return false;\r
- } \r
- };\r
- \r
- this.handleArrowKeyPress = function (evt) {\r
- if (thisReorderer.activeItem) {\r
- switch (evt.keyCode) {\r
- case fluid.keys.DOWN:\r
- evt.preventDefault();\r
- thisReorderer.handleDownArrow (evt.ctrlKey); \r
- return false;\r
- case fluid.keys.UP: \r
- evt.preventDefault();\r
- thisReorderer.handleUpArrow (evt.ctrlKey); \r
- return false;\r
- case fluid.keys.LEFT: \r
- evt.preventDefault();\r
- thisReorderer.handleLeftArrow (evt.ctrlKey); \r
- return false;\r
- case fluid.keys.RIGHT: \r
- evt.preventDefault();\r
- thisReorderer.handleRightArrow (evt.ctrlKey); \r
- return false;\r
- default:\r
- return true;\r
- }\r
- }\r
- };\r
- \r
- this.handleUpArrow = function (isCtrl) {\r
- if (isCtrl) {\r
- this.layoutHandler.moveItemUp (this.activeItem);\r
- this.findElementToFocus (this.activeItem).focus();\r
- } else {\r
- this.focusItem (this.layoutHandler.getItemAbove(this.activeItem));\r
- } \r
- };\r
- \r
- this.handleDownArrow = function (isCtrl) {\r
- if (isCtrl) {\r
- this.layoutHandler.moveItemDown (this.activeItem);\r
- this.findElementToFocus (this.activeItem).focus();\r
- } else {\r
- this.focusItem (this.layoutHandler.getItemBelow (this.activeItem));\r
- }\r
- };\r
- \r
- this.handleRightArrow = function (isCtrl) {\r
- if (isCtrl) {\r
- this.layoutHandler.moveItemRight (this.activeItem);\r
- jQuery(this.findElementToFocus (this.activeItem)).focus();\r
- } else {\r
- this.focusItem (this.layoutHandler.getRightSibling (this.activeItem)); \r
- } \r
- };\r
- \r
- this.handleLeftArrow = function (isCtrl) {\r
- if (isCtrl) {\r
- this.layoutHandler.moveItemLeft (this.activeItem);\r
- this.findElementToFocus (this.activeItem).focus();\r
- } else {\r
- this.focusItem (this.layoutHandler.getLeftSibling (this.activeItem)); \r
- }\r
- };\r
- \r
- this._fetchMessage = function (messagekey) {\r
- var messageID = this.messageNamebase + messagekey;\r
- var node = document.getElementById (messageID);\r
- \r
- return node? node.innerHTML: "[Message not found at id " + messageID + "]";\r
- };\r
- \r
- this._setActiveItem = function (anItem) {\r
- this.activeItem = anItem;\r
- this._updateActiveDescendent();\r
- };\r
- \r
- this._updateActiveDescendent = function() {\r
- if (this.activeItem) {\r
- this.domNode.attr ("aaa:activedescendent", this.activeItem.id);\r
- } else {\r
- this.domNode.removeAttr ("aaa:activedescendent");\r
- }\r
- };\r
-\r
- var dropMarker; // private scratch variable\r
- \r
- /**\r
- * evt.data - the droppable DOM element.\r
- */\r
- function trackMouseMovement (evt) {\r
- if (thisReorderer.layoutHandler.isMouseBefore (evt, evt.data)) {\r
- jQuery (evt.data).before (dropMarker);\r
- } \r
- else {\r
- jQuery (evt.data).after (dropMarker);\r
- }\r
- }\r
-\r
- /**\r
- * Given an item, make it draggable.\r
- */\r
- function setUpDraggable (anItem) {\r
- anItem.draggable ({\r
- helper: function() {\r
- theAvatar = jQuery (this).clone();\r
- jQuery (theAvatar).removeAttr ("id");\r
- jQuery ("[id]", theAvatar).removeAttr ("id");\r
- jQuery (":hidden", theAvatar).remove(); \r
- jQuery ("input", theAvatar).attr ("disabled", "true"); \r
- theAvatar.addClass (thisReorderer.cssClasses.avatar); \r
- return theAvatar;\r
- },\r
- start: function (e, ui) {\r
- thisReorderer.focusItem (ui.draggable.element); \r
- jQuery (ui.draggable.element).addClass (thisReorderer.cssClasses.dragging);\r
- ui.draggable.element.setAttribute ("aaa:grab", "true");\r
- \r
- // In order to create valid html, the drop marker is the same type as the node being dragged.\r
- // This creates a confusing UI in cases such as an ordered list. \r
- // drop marker functionality should be made pluggable. \r
- dropMarker = document.createElement (ui.draggable.element.tagName);\r
- jQuery (dropMarker).addClass (thisReorderer.cssClasses.dropMarker);\r
- dropMarker.style.visibility = "hidden";\r
- },\r
- stop: function(e, ui) {\r
- jQuery (ui.draggable.element).removeClass (thisReorderer.cssClasses.dragging);\r
- thisReorderer.activeItem.setAttribute ("aaa:grab", "supported");\r
- thisReorderer.domNode.focus();\r
- if (dropMarker.parentNode) {\r
- dropMarker.parentNode.removeChild (dropMarker);\r
- }\r
- theAvatar = null;\r
- }\r
- });\r
- \r
- } // end setUpDraggable()\r
-\r
- /**\r
- * Make item a drop target.\r
- */\r
- function setUpDroppable (anItem, selector) {\r
- anItem.droppable ({\r
- accept: selector,\r
- tolerance: "pointer",\r
- over: function (e, ui) {\r
- // the second parameter to bind() can be accessed through the event as event.data\r
- jQuery (ui.droppable.element).bind ("mousemove", ui.droppable.element, trackMouseMovement); \r
- jQuery (theAvatar).bind ("mousemove", ui.droppable.element, trackMouseMovement); \r
- dropMarker.style.visibility = "visible";\r
- },\r
- out: function (e, ui) {\r
- dropMarker.style.visibility = "hidden";\r
- jQuery (ui.droppable.element).unbind ("mousemove", trackMouseMovement);\r
- jQuery (theAvatar).unbind ("mousemove", trackMouseMovement); \r
- },\r
- drop: function (e, ui) {\r
- thisReorderer.layoutHandler.mouseMoveItem(e, ui.draggable.element, ui.droppable.element);\r
- jQuery (ui.droppable.element).unbind ("mousemove", trackMouseMovement);\r
- jQuery (theAvatar).unbind ("mousemove", trackMouseMovement); \r
- }\r
- });\r
- \r
- } // end setUpDroppable().\r
- \r
- /**\r
- * Given an item, make it a draggable and a droppable with the relevant properties and functions.\r
- * @param anItem The element to make draggable and droppable.\r
- * @param selector The jQuery selector(s) that select all the orderables.\r
- */ \r
- function setUpDnDItem (anItem, selector) {\r
- anItem.mouseover ( \r
- function () {\r
- jQuery (this).addClass (thisReorderer.cssClasses.hover);\r
- }\r
- );\r
- \r
- anItem.mouseout ( \r
- function () {\r
- jQuery (this).removeClass (thisReorderer.cssClasses.hover);\r
- }\r
- );\r
- \r
- // Make anItem draggable and a drop target (in the jQuery UI sense).\r
- setUpDraggable (anItem);\r
- setUpDroppable (anItem, selector);\r
- \r
- } // end setUpDnDItem()\r
- \r
- function initOrderables() {\r
- var items = thisReorderer.orderableFinder (thisReorderer.domNode.get(0));\r
- if (items.length === 0) {\r
- return;\r
- }\r
- \r
- // Create a selector based on the ids of the nodes for use with drag and drop.\r
- // This should be replaced with using the actual nodes rather then a selector \r
- // but will require a patch to jquery's DnD. \r
- // See: FLUID-71, FLUID-112\r
- var selector = "";\r
- for (var i = 0; i < items.length; i++) {\r
- var item = items[i];\r
- selector += "[id=" + item.id + "]";\r
- if (i !== items.length - 1) {\r
- selector += ", ";\r
- } \r
- }\r
-\r
- // Setup orderable item including drag and drop.\r
- for (i = 0; i < items.length; i++) {\r
- jQuery (items[i]).addClass (thisReorderer.cssClasses.defaultStyle);\r
- setUpDnDItem (jQuery (items[i]), selector);\r
- }\r
- \r
- // Add any other drop targets (e.g., any unmoveable ones).\r
- if ((thisReorderer.droppableFinder) && (thisReorderer.droppableFinder.constructor === Function)) {\r
- var extraDropTargets = thisReorderer.droppableFinder (thisReorderer.domNode);\r
- for (i = 0; i < extraDropTargets.length; i++) {\r
- var jExtraDropTarget = jQuery (extraDropTargets[i]);\r
- if (!jExtraDropTarget.droppableInstance()) {\r
- setUpDroppable (jExtraDropTarget, selector);\r
- }\r
- }\r
- }\r
- } // end initOrderables().\r
-\r
- /**\r
- * Finds the "orderable" parent element given a child element.\r
- */\r
- this._findReorderableParent = function (childElement, items) {\r
- if (!childElement) {\r
- return null;\r
- }\r
- else {\r
- for (var i=0; i<items.length; i++) {\r
- if (childElement === items[i]) {\r
- return childElement;\r
- } \r
- }\r
- return this._findReorderableParent (childElement.parentNode, items);\r
- }\r
- };\r
-\r
- // Final initialization of the Reorderer at the end of the construction process \r
- if (this.domNode) {\r
- setupDomNode(this.domNode);\r
- initOrderables();\r
- }\r
-}; // End Reorderer\r
-\r
-/*******************\r
- * Layout Handlers *\r
- *******************/\r
-(function () {\r
- // Shared private functions.\r
- var moveItem = function (item, relatedItemInfo, defaultPlacement, wrappedPlacement) {\r
- var itemPlacement = defaultPlacement;\r
- if (relatedItemInfo.hasWrapped) {\r
- itemPlacement = wrappedPlacement;\r
- }\r
- \r
- // The "after" and "before" strings are an artifact of using dojo.place. \r
- // This should be refactored. \r
- if (itemPlacement === "after") {\r
- jQuery (relatedItemInfo.item).after (item);\r
- } else {\r
- jQuery (relatedItemInfo.item).before (item);\r
- } \r
- };\r
- \r
- /**\r
- * For drag-and-drop during the drag: is the mouse over the "before" half\r
- * of the droppable? In the case of a vertically oriented set of orderables,\r
- * "before" means "above". For a horizontally oriented set, "before" means\r
- * "left of".\r
- */\r
- var isMouseBefore = function (evt, droppableEl, orientation) {\r
- var mid;\r
- if (orientation === fluid.orientation.VERTICAL) {\r
- mid = jQuery (droppableEl).offset().top + (droppableEl.offsetHeight / 2);\r
- return (evt.pageY < mid);\r
- } else {\r
- mid = jQuery (droppableEl).offset().left + (droppableEl.offsetWidth / 2);\r
- return (evt.clientX < mid);\r
- }\r
- }; \r
-\r
- var itemInfoFinders = {\r
- /*\r
- * A general get{Left|Right}SiblingInfo() given an item, a list of orderables and a direction.\r
- * The direction is encoded by either a +1 to move right, or a -1 to\r
- * move left, and that value is used internally as an increment or\r
- * decrement, respectively, of the index of the given item.\r
- */\r
- getSiblingInfo: function (item, orderables, /* +1, -1 */ incDecrement) {\r
- var index = jQuery (orderables).index (item) + incDecrement;\r
- var hasWrapped = false;\r
- \r
- // Handle wrapping to 'before' the beginning. \r
- if (index === -1) {\r
- index = orderables.length - 1;\r
- hasWrapped = true;\r
- }\r
- // Handle wrapping to 'after' the end.\r
- else if (index === orderables.length) {\r
- index = 0;\r
- hasWrapped = true;\r
- } \r
- // Handle case where the passed-in item is *not* an "orderable"\r
- // (or other undefined error).\r
- //\r
- else if (index < -1 || index > orderables.length) {\r
- index = 0;\r
- }\r
- \r
- return {item: orderables[index], hasWrapped: hasWrapped};\r
- },\r
-\r
- /*\r
- * Returns an object containing the item that is to the right of the given item\r
- * and a flag indicating whether or not the process has 'wrapped' around the end of\r
- * the row that the given item is in\r
- */\r
- getRightSiblingInfo: function (item, orderables) {\r
- return this.getSiblingInfo (item, orderables, 1);\r
- },\r
- \r
- /*\r
- * Returns an object containing the item that is to the left of the given item\r
- * and a flag indicating whether or not the process has 'wrapped' around the end of\r
- * the row that the given item is in\r
- */\r
- getLeftSiblingInfo: function (item, orderables) {\r
- return this.getSiblingInfo (item, orderables, -1);\r
- },\r
- \r
- /*\r
- * Returns an object containing the item that is below the given item in the current grid\r
- * and a flag indicating whether or not the process has 'wrapped' around the end of\r
- * the column that the given item is in. The flag is necessary because when an image is being\r
- * moved to the resulting item location, the decision of whether or not to insert before or\r
- * after the item changes if the process wrapped around the column.\r
- */\r
- getItemInfoBelow: function (inItem, orderables) {\r
- var curCoords = jQuery (inItem).offset();\r
- var i, iCoords;\r
- var firstItemInColumn, currentItem;\r
- \r
- for (i = 0; i < orderables.length; i++) {\r
- currentItem = orderables [i];\r
- iCoords = jQuery (orderables[i]).offset();\r
- if (iCoords.left === curCoords.left) {\r
- firstItemInColumn = firstItemInColumn || currentItem;\r
- if (iCoords.top > curCoords.top) {\r
- return {item: currentItem, hasWrapped: false};\r
- }\r
- }\r
- }\r
- \r
- firstItemInColumn = firstItemInColumn || orderables [0];\r
- return {item: firstItemInColumn, hasWrapped: true};\r
- },\r
- \r
- /*\r
- * Returns an object containing the item that is above the given item in the current grid\r
- * and a flag indicating whether or not the process has 'wrapped' around the end of\r
- * the column that the given item is in. The flag is necessary because when an image is being\r
- * moved to the resulting item location, the decision of whether or not to insert before or\r
- * after the item changes if the process wrapped around the column.\r
- */\r
- getItemInfoAbove: function (inItem, orderables) {\r
- var curCoords = jQuery (inItem).offset();\r
- var i, iCoords;\r
- var lastItemInColumn, currentItem;\r
- \r
- for (i = orderables.length - 1; i > -1; i--) {\r
- currentItem = orderables [i];\r
- iCoords = jQuery (orderables[i]).offset();\r
- if (iCoords.left === curCoords.left) {\r
- lastItemInColumn = lastItemInColumn || currentItem;\r
- if (curCoords.top > iCoords.top) {\r
- return {item: currentItem, hasWrapped: false};\r
- }\r
- }\r
- }\r
- \r
- lastItemInColumn = lastItemInColumn || orderables [0];\r
- return {item: lastItemInColumn, hasWrapped: true};\r
- }\r
- \r
- };\r
- \r
- // Public layout handlers.\r
- fluid.ListLayoutHandler = function (params) {\r
- var orderableFinder = params.orderableFinder;\r
- var container = params.container;\r
- var orderChangedCallback = params.orderChangedCallback;\r
- var orientation = params.orientation;\r
- \r
- // Unwrap the container if it's a jQuery.\r
- container = (container.jquery) ? container.get(0) : container;\r
- \r
- orderChangedCallback = orderChangedCallback || function () {};\r
- \r
- orientation = orientation || fluid.orientation.VERTICAL; // default\r
- \r
- this.getRightSibling = function (item) {\r
- return itemInfoFinders.getRightSiblingInfo(item, orderableFinder(container)).item;\r
- };\r
- \r
- this.moveItemRight = function (item) {\r
- moveItem (item, itemInfoFinders.getRightSiblingInfo(item, orderableFinder(container)), "after", "before");\r
- orderChangedCallback();\r
- };\r
- \r
- this.getLeftSibling = function (item) {\r
- return itemInfoFinders.getLeftSiblingInfo(item, orderableFinder(container)).item;\r
- };\r
- \r
- this.moveItemLeft = function (item) {\r
- moveItem (item, itemInfoFinders.getLeftSiblingInfo(item, orderableFinder(container)), "before", "after");\r
- orderChangedCallback();\r
- };\r
- \r
- this.getItemBelow = this.getRightSibling;\r
- \r
- this.getItemAbove = this.getLeftSibling;\r
- \r
- this.moveItemUp = this.moveItemLeft;\r
- \r
- this.moveItemDown = this.moveItemRight;\r
- \r
- this.isMouseBefore = function(evt, droppableEl) {\r
- return isMouseBefore(evt, droppableEl, orientation);\r
- };\r
- \r
- this.mouseMoveItem = function (e, item, relatedItem) {\r
- if (this.isMouseBefore (e, relatedItem)) {\r
- jQuery (relatedItem).before (item);\r
- } else {\r
- jQuery (relatedItem).after (item);\r
- }\r
- orderChangedCallback(); \r
- };\r
- }; // End ListLayoutHandler\r
- \r
- /*\r
- * Items in the Lightbox are stored in a list, but they are visually presented as a grid that\r
- * changes dimensions when the window changes size. As a result, when the user presses the up or\r
- * down arrow key, what lies above or below depends on the current window size.\r
- * \r
- * The GridLayoutHandler is responsible for handling changes to this virtual 'grid' of items\r
- * in the window, and of informing the Lightbox of which items surround a given item.\r
- */\r
- fluid.GridLayoutHandler = function (params) {\r
- fluid.ListLayoutHandler.call (this, params);\r
-\r
- var orderableFinder = params.orderableFinder;\r
- var container = params.container;\r
- var orderChangedCallback = params.orderChangedCallback;\r
- var orientation = fluid.orientation.HORIZONTAL;\r
- \r
- // Unwrap the container if it's a jQuery.\r
- container = (container.jquery) ? container.get(0) : container;\r
- \r
- orderChangedCallback = orderChangedCallback || function () {};\r
- \r
- this.getItemBelow = function(item) {\r
- return itemInfoFinders.getItemInfoBelow (item, orderableFinder(container)).item;\r
- };\r
- \r
- this.moveItemDown = function (item) {\r
- moveItem (item, itemInfoFinders.getItemInfoBelow (item, orderableFinder(container)), "after", "before");\r
- orderChangedCallback(); \r
- };\r
- \r
- this.getItemAbove = function (item) {\r
- return itemInfoFinders.getItemInfoAbove (item, orderableFinder(container)).item; \r
- }; \r
- \r
- this.moveItemUp = function (item) {\r
- moveItem (item, itemInfoFinders.getItemInfoAbove (item, orderableFinder(container)), "before", "after");\r
- orderChangedCallback(); \r
- };\r
- \r
- // We need to override ListLayoutHandler.isMouseBefore to ensure that the local private\r
- // orientation is used.\r
- this.isMouseBefore = function(evt, droppableEl) {\r
- return isMouseBefore(evt, droppableEl, orientation);\r
- };\r
- \r
- }; // End of GridLayoutHandler\r
-\r
- fluid.portletPerms = fluid.portletPerms || {};\r
- fluid.portletPerms.canDrop = function (perms, indexOfItem, indexOfTarget, position) {\r
- return (!!perms[indexOfItem][indexOfTarget][position]); \r
- };\r
- \r
- /*\r
- * PortletLayout helper object.\r
- */\r
- fluid.PortletLayout = function() {\r
- \r
- /**\r
- * Calculate the location of the item and the column in which it resides.\r
- * @return An object with column index and item index (within that column) properties.\r
- * These indices are -1 if the item does not exist in the grid.\r
- */\r
- this.calcColumnAndItemIndex = function (item, portletLayoutJSON) {\r
- var indices = { columnIndex: -1, itemIndex: -1 };\r
- for (var col = 0; col < portletLayoutJSON.columns.length; col++) {\r
- var itemIndex = jQuery (portletLayoutJSON.columns[col].children).index (item.id);\r
- if (itemIndex !== -1) {\r
- indices.columnIndex = col;\r
- indices.itemIndex = itemIndex;\r
- break;\r
- } \r
- }\r
- return indices;\r
- };\r
- \r
- /**\r
- * Return the first orderable item in the given column.\r
- */\r
- this.findFirstOrderableSiblingInColumn = function (columnIndex, orderableItems, portletLayoutJSON) {\r
- // Pull out the portlet id of the top-most sibling in the column.\r
- var topMostOrderableSibling = null;\r
- var itemIndex = 0;\r
- var column = portletLayoutJSON.columns[columnIndex];\r
- if (column) {\r
- var id = column.children[itemIndex];\r
- topMostOrderableSibling = jQuery ("#" + id)[0];\r
- // loop down the column looking for first orderable portlet (i.e. skip over non-movable portlets)\r
- while (orderableItems.index (topMostOrderableSibling) === -1) {\r
- itemIndex += 1;\r
- id = column.children[itemIndex];\r
- topMostOrderableSibling = jQuery ("#" + id).get (0);\r
- }\r
- }\r
- return topMostOrderableSibling;\r
- };\r
- \r
- /**\r
- * Return the number of items in the given column. If the column index\r
- * is out of bounds, this returns -1.\r
- */\r
- this.numItemsInColumn = function (columnIndex, portletLayoutJSON) {\r
- if ((columnIndex < 0) || (columnIndex > portletLayoutJSON.columns.length)) {\r
- return -1;\r
- }\r
- else {\r
- return portletLayoutJSON.columns[columnIndex].children.length;\r
- }\r
- };\r
- \r
- this.numColumns = function (layout) {\r
- return layout.columns.length;\r
- };\r
- \r
- this.findLinearIndex = function (itemId, layout) {\r
- var columns = layout.columns;\r
- var linearIndex = 0;\r
- \r
- for (var i = 0; i < columns.length; i++) {\r
- var idsInCol = columns[i].children;\r
- for (var j = 0; j < idsInCol.length; j++) {\r
- if (idsInCol[j] === itemId) {\r
- return linearIndex;\r
- }\r
- linearIndex++;\r
- }\r
- }\r
- \r
- return -1;\r
- };\r
- \r
- /**\r
- * Move an item within the portletLayoutJSON object. \r
- */\r
- this.updateLayout = function (item, relativeItem, placement, portletLayoutJSON) {\r
- if (!item || !relativeItem) { return; }\r
- var itemIndices = this.calcColumnAndItemIndex (item, portletLayoutJSON);\r
-\r
- var itemId = portletLayoutJSON.columns[itemIndices.columnIndex].children[itemIndices.itemIndex];\r
- portletLayoutJSON.columns[itemIndices.columnIndex].children.splice (itemIndices.itemIndex, 1);\r
-\r
- var relativeItemIndices = this.calcColumnAndItemIndex (relativeItem, portletLayoutJSON);\r
- var targetCol = portletLayoutJSON.columns[relativeItemIndices.columnIndex].children;\r
- targetCol.splice (relativeItemIndices.itemIndex + (placement === "before"? 0 : 1), 0, itemId);\r
- };\r
- }; // End of PortletLayout.\r
- \r
- /*\r
- * Portlet Layout Handler for reordering portlet-type markup.\r
- * \r
- * General movement guidelines:\r
- * \r
- * - Arrowing sideways will always go to the top (moveable) portlet in the column\r
- * - Moving sideways will always move to the top available drop target in the column\r
- * - Wrapping is not necessary at this first pass, but is ok\r
- */\r
- fluid.PortletLayoutHandler = function (params) {\r
- var orderableFinder = params.orderableFinder;\r
- var container = params.container;\r
- var orderChangedCallback = params.orderChangedCallback;\r
- var portletLayoutJSON = params.portletLayout;\r
- var targetPerms = params.dropTargetPermissions;\r
- var orientation = fluid.orientation.VERTICAL;\r
- \r
- var portletLayout = new fluid.PortletLayout();\r
- \r
- // Unwrap the container if it's a jQuery.\r
- container = (container.jquery) ? container.get(0) : container;\r
- \r
- orderChangedCallback = orderChangedCallback || function () {};\r
-\r
- // Private Methods.\r
- /*\r
- * A general get{Above|Below}Sibling() given an item and a direction.\r
- * The direction is encoded by either a +1 to move down, or a -1 to\r
- * move up, and that value is used internally as an increment or\r
- * decrement, respectively, of the index of the given item.\r
- * This implementation does not wrap around. \r
- */\r
- var getVerticalSibling = function (item, /* +1, -1 */ incDecrement) {\r
- var orderables = orderableFinder (container);\r
- var index = jQuery(orderables).index(item) + incDecrement;\r
- \r
- // If we wrap, backup \r
- if ((index === -1) || (index === orderables.length)) {\r
- return null;\r
- }\r
- // Handle case where the passed-in item is *not* an "orderable"\r
- // (or other undefined error).\r
- //\r
- else if (index < -1 || index > orderables.length) {\r
- index = 0;\r
- }\r
- \r
- return orderables[index];\r
- };\r
- \r
- /*\r
- * A general get{Beside}SiblingInfo() given an item and a direction.\r
- * The direction is encoded by either a +1 to move right, or a -1 to\r
- * move left.\r
- * Currently, the horizontal sibling defaults to the top orderable item in the\r
- * neighboring column.\r
- */\r
- var getHorizontalSibling = function (item, /* +1, -1 */ incDecrement) {\r
- var orderables = orderableFinder (container);\r
- \r
- // go through all the children and find which column the passed in item is located in.\r
- // Save that column if found.\r
- var colIndex = portletLayout.calcColumnAndItemIndex (item, portletLayoutJSON).columnIndex;\r
- if (colIndex === -1) {\r
- return null;\r
- }\r
- var sibling = portletLayout.findFirstOrderableSiblingInColumn (colIndex + incDecrement, orderables, portletLayoutJSON);\r
- return sibling;\r
- \r
- };\r
- \r
- // These methods are public only for our unit tests. They need to be refactored into a portletlayout object.\r
- var isFirstInColumn = function (item) {\r
- var position = portletLayout.calcColumnAndItemIndex (item, portletLayoutJSON);\r
- return (position.itemIndex === 0) ? true : false;\r
- };\r
- \r
- var isLastInColumn = function (item) {\r
- var position = portletLayout.calcColumnAndItemIndex (item, portletLayoutJSON);\r
- return (position.itemIndex === portletLayout.numItemsInColumn (position.columnIndex, portletLayoutJSON)-1) ? true : false;\r
- };\r
- \r
- var isInLeftmostColumn = function (item) {\r
- if (portletLayout.calcColumnAndItemIndex (item, portletLayoutJSON).columnIndex === 0) {\r
- return true;\r
- } else {\r
- return false;\r
- }\r
- };\r
- \r
- var isInRightmostColumn = function (item) {\r
- if (portletLayout.calcColumnAndItemIndex (item, portletLayoutJSON).columnIndex === portletLayout.numColumns (portletLayoutJSON) - 1) {\r
- return true;\r
- } else {\r
- return false;\r
- }\r
- };\r
- \r
- var canDrop = function (itemId, relatedItemId, position) {\r
- return fluid.portletPerms.canDrop(\r
- targetPerms, \r
- portletLayout.findLinearIndex(itemId, portletLayoutJSON), \r
- portletLayout.findLinearIndex(relatedItemId, portletLayoutJSON), \r
- position);\r
- };\r
- \r
- var moveBefore = function (item, relatedItem) {\r
- if (!canDrop (item.id, relatedItem.id, fluid.position.BEFORE)) {\r
- return;\r
- } \r
- \r
- jQuery (relatedItem).before (item);\r
- portletLayout.updateLayout (item, relatedItem, "before", portletLayoutJSON);\r
- portletLayoutJSON = orderChangedCallback() || portletLayoutJSON; \r
- };\r
- \r
- var moveAfter = function (item, relatedItem) {\r
- if (!canDrop (item.id, relatedItem.id, fluid.position.AFTER)) {\r
- return;\r
- } \r
- \r
- jQuery (relatedItem).after (item);\r
- portletLayout.updateLayout (item, relatedItem, "after", portletLayoutJSON);\r
- portletLayoutJSON = orderChangedCallback() || portletLayoutJSON; \r
- }; \r
-\r
- // Public Methods\r
- \r
- this.getRightSibling = function (item) {\r
- if (isInRightmostColumn(item)) {\r
- return item;\r
- } else {\r
- return getHorizontalSibling(item, 1);\r
- }\r
- };\r
- \r
- this.moveItemRight = function (item) {\r
- var rightSibling = this.getRightSibling (item);\r
- jQuery (rightSibling).before (item);\r
- portletLayout.updateLayout (item, rightSibling, "before", portletLayoutJSON);\r
- // first, stringify the json before sending it\r
- portletLayoutJSON = orderChangedCallback() || portletLayoutJSON; \r
- // the return value will actually be a big json object with two parts\r
- // we'll have to parse it, and separate the two parts\r
- };\r
- \r
- this.getLeftSibling = function (item) {\r
- if (isInLeftmostColumn(item)) {\r
- return item;\r
- } else {\r
- return getHorizontalSibling(item, -1);\r
- }\r
- };\r
- \r
- this.moveItemLeft = function (item) {\r
- var leftSibling = this.getLeftSibling(item);\r
- jQuery (leftSibling).before (item);\r
- portletLayout.updateLayout (item, leftSibling, "before", portletLayoutJSON);\r
- portletLayoutJSON = orderChangedCallback() || portletLayoutJSON; \r
- };\r
- \r
- this.getItemAbove = function (item) {\r
- if (isFirstInColumn(item)) {\r
- return item;\r
- } else {\r
- return getVerticalSibling (item, -1);\r
- }\r
- };\r
- \r
- this.moveItemUp = function (item) {\r
- var siblingAbove = this.getItemAbove(item);\r
- moveBefore (item, siblingAbove);\r
- };\r
- \r
- this.getItemBelow = function (item) {\r
- if (isLastInColumn(item)) {\r
- return item;\r
- } else {\r
- return getVerticalSibling (item, 1);\r
- }\r
- };\r
- \r
- this.moveItemDown = function (item) {\r
- var siblingBelow = this.getItemBelow(item);\r
- moveAfter (item, siblingBelow);\r
- };\r
- \r
- this.isMouseBefore = function(evt, droppableEl) {\r
- return isMouseBefore(evt, droppableEl, orientation);\r
- };\r
-\r
- this.mouseMoveItem = function (e, item, relatedItem) {\r
- if (this.isMouseBefore (e, relatedItem)) {\r
- moveBefore (item, relatedItem);\r
- } else {\r
- moveAfter (item, relatedItem);\r
- }\r
- };\r
- \r
- }; // End PortalLayoutHandler\r
-}) ();\r
-\r