/* * Software License Agreement (BSD License) * * Copyright (c) 2007, Parakey Inc. * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * * Neither the name of Parakey Inc. nor the names of its * contributors may be used to endorse or promote products * derived from this software without specific prior * written permission of Parakey Inc. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Creator: * Joe Hewitt * Contributors * John J. Barton (IBM Almaden) * Jan Odvarko (Mozilla Corp.) * Max Stepanov (Aptana Inc.) * Rob Campbell (Mozilla Corp.) * Hans Hillen (Paciello Group, Mozilla) * Curtis Bartley (Mozilla Corp.) * Mike Collins (IBM Almaden) * Kevin Decker * Mike Ratcliffe (Comartis AG) * Hernan Rodríguez Colmeiro * Austin Andrews * Christoph Dorn * Steven Roussey (AppCenter Inc, Network54) */ /////////////////////////////////////////////////////////////////////////// //// InsideOutBox /** * InsideOutBoxView is a simple interface definition for views implementing * InsideOutBox controls. All implementors must define these methods. * Implemented in InspectorUI. */ /* InsideOutBoxView = { // * Retrieves the parent object for a given child object. * @param aChild * The child node to retrieve the parent object for. * @returns a DOM node | null // getParentObject: function(aChild) {}, // * Retrieves a given child node. * * If both index and previousSibling are passed, the implementation * may assume that previousSibling will be the return for getChildObject * with index-1. * @param aParent * The parent object of the child object to retrieve. * @param aIndex * The index of the child object to retrieve from aParent. * @param aPreviousSibling * The previous sibling of the child object to retrieve. * Supercedes aIndex. * @returns a DOM object | null // getChildObject: function(aParent, aIndex, aPreviousSibling) {}, // * Renders the HTML representation of the object. Should return an HTML * object which will be displayed to the user. * @param aObject * The object to create the box object for. * @param aIsRoot * Is the object the root object. May not be used in all * implementations. * @returns an object box | null // createObjectBox: function(aObject, aIsRoot) {}, // * Convenience wrappers for classList API. * @param aObject * DOM node to query/set. * @param aClassName * String containing the class name to query/set. // hasClass: function(aObject, aClassName) {}, addClass: function(aObject, aClassName) {}, removeClass: function(aObject, aClassName) {} }; */ /** * Creates a tree based on objects provided by a separate "view" object. * * Construction uses an "inside-out" algorithm, meaning that the view's job is * first to tell us the ancestry of each object, and secondarily its * descendants. * * Constructor * @param aView * The view requiring the InsideOutBox. * @param aBox * The box object containing the InsideOutBox. Required to add/remove * children during box manipulation (toggling opened or closed). */ function InsideOutBox(aView, aBox) { this.view = aView; this.box = aBox; this.rootObject = null; this.rootObjectBox = null; this.selectedObjectBox = null; this.highlightedObjectBox = null; this.scrollIntoView = false; }; InsideOutBox.prototype = { /** * Highlight the given object node in the tree. * @param aObject * the object to highlight. * @returns objectBox */ highlight: function IOBox_highlight(aObject) { let objectBox = this.createObjectBox(aObject); this.highlightObjectBox(objectBox); return objectBox; }, /** * Open the given object node in the tree. * @param aObject * The object node to open. * @returns objectBox */ openObject: function IOBox_openObject(aObject) { let object = aObject; let firstChild = this.view.getChildObject(object, 0); if (firstChild) object = firstChild; return this.openToObject(object); }, /** * Open the tree up to the given object node. * @param aObject * The object in the tree to open to. * @returns objectBox */ openToObject: function IOBox_openToObject(aObject) { let objectBox = this.createObjectBox(aObject); this.openObjectBox(objectBox); return objectBox; }, /** * Select the given object node in the tree. * @param aObject * The object node to select. * @param makeBoxVisible * Boolean. Open the object box in the tree? * @param forceOpen * Force the object box open by expanding all elements in the tree? * @param scrollIntoView * Scroll the objectBox into view? * @returns nsIDOMNode|null * A DOM node that represents the "object box", the element that * holds/displays the given aObject representation in the tree. If * the object cannot be selected, if it is a stale object, null is * returned. */ select: function IOBox_select(aObject, makeBoxVisible, forceOpen, scrollIntoView) { let objectBox = this.createObjectBox(aObject); if (!objectBox) { return null; } this.selectObjectBox(objectBox, forceOpen); if (makeBoxVisible) { this.openObjectBox(objectBox); if (scrollIntoView) { objectBox.scrollIntoView(true); } } return objectBox; }, /** * Expands/contracts the given object, depending on its state. * @param aObject * The tree node to expand/contract. */ toggleObject: function IOBox_toggleObject(aObject) { let box = this.createObjectBox(aObject); if (!(this.view.hasClass(box, "open"))) this.expandObjectBox(box); else this.contractObjectBox(box); }, /** * Expand the given object in the tree. * @param aObject * The tree node to expand. */ expandObject: function IOBox_expandObject(aObject) { let objectBox = this.createObjectBox(aObject); if (objectBox) this.expandObjectBox(objectBox); }, /** * Contract the given object in the tree. * @param aObject * The tree node to contract. */ contractObject: function IOBox_contractObject(aObject) { let objectBox = this.createObjectBox(aObject); if (objectBox) this.contractObjectBox(objectBox); }, /** * General method for iterating over an object's ancestors and performing * some function. * @param aObject * The object whose ancestors we wish to iterate over. * @param aCallback * The function to call with the object as argument. */ iterateObjectAncestors: function IOBox_iterateObjectAncesors(aObject, aCallback) { let object = aObject; if (!(aCallback && typeof(aCallback) == "function")) { this.view._log("Illegal argument in IOBox.iterateObjectAncestors"); return; } while ((object = this.getParentObjectBox(object))) aCallback(object); }, /** * Highlight the given objectBox in the tree. * @param aObjectBox * The objectBox to highlight. */ highlightObjectBox: function IOBox_highlightObjectBox(aObjectBox) { let self = this; if (!aObjectBox) return; if (this.highlightedObjectBox) { this.view.removeClass(this.highlightedObjectBox, "highlighted"); this.iterateObjectAncestors(this.highlightedObjectBox, function (box) { self.view.removeClass(box, "highlightOpen"); }); } this.highlightedObjectBox = aObjectBox; this.view.addClass(aObjectBox, "highlighted"); this.iterateObjectAncestors(this.highlightedObjectBox, function (box) { self.view.addClass(box, "highlightOpen"); }); aObjectBox.scrollIntoView(true); }, /** * Select the given objectBox in the tree, forcing it to be open if necessary. * @param aObjectBox * The objectBox to select. * @param forceOpen * Force the box (subtree) to be open? */ selectObjectBox: function IOBox_selectObjectBox(aObjectBox, forceOpen) { let isSelected = this.selectedObjectBox && aObjectBox == this.selectedObjectBox; // aObjectBox is already selected, return if (isSelected) return; if (this.selectedObjectBox) this.view.removeClass(this.selectedObjectBox, "selected"); this.selectedObjectBox = aObjectBox; if (aObjectBox) { this.view.addClass(aObjectBox, "selected"); // Force it open the first time it is selected if (forceOpen) this.expandObjectBox(aObjectBox, true); } }, /** * Open the ancestors of the given object box. * @param aObjectBox * The object box to open. */ openObjectBox: function IOBox_openObjectBox(aObjectBox) { if (!aObjectBox) return; let self = this; this.iterateObjectAncestors(aObjectBox, function (box) { self.view.addClass(box, "open"); let labelBox = box.querySelector(".nodeLabelBox"); if (labelBox) labelBox.setAttribute("aria-expanded", "true"); }); }, /** * Expand the given object box. * @param aObjectBox * The object box to expand. */ expandObjectBox: function IOBox_expandObjectBox(aObjectBox) { let nodeChildBox = this.getChildObjectBox(aObjectBox); // no children means nothing to expand, return if (!nodeChildBox) return; if (!aObjectBox.populated) { let firstChild = this.view.getChildObject(aObjectBox.repObject, 0); this.populateChildBox(firstChild, nodeChildBox); } let labelBox = aObjectBox.querySelector(".nodeLabelBox"); if (labelBox) labelBox.setAttribute("aria-expanded", "true"); this.view.addClass(aObjectBox, "open"); }, /** * Contract the given object box. * @param aObjectBox * The object box to contract. */ contractObjectBox: function IOBox_contractObjectBox(aObjectBox) { this.view.removeClass(aObjectBox, "open"); let nodeLabel = aObjectBox.querySelector(".nodeLabel"); let labelBox = nodeLabel.querySelector(".nodeLabelBox"); if (labelBox) labelBox.setAttribute("aria-expanded", "false"); }, /** * Toggle the given object box, forcing open if requested. * @param aObjectBox * The object box to toggle. * @param forceOpen * Force the objectbox open? */ toggleObjectBox: function IOBox_toggleObjectBox(aObjectBox, forceOpen) { let isOpen = this.view.hasClass(aObjectBox, "open"); if (!forceOpen && isOpen) this.contractObjectBox(aObjectBox); else if (!isOpen) this.expandObjectBox(aObjectBox); }, /** * Creates all of the boxes for an object, its ancestors, and siblings. * @param aObject * The tree node to create the object boxes for. * @returns anObjectBox or null */ createObjectBox: function IOBox_createObjectBox(aObject) { if (!aObject) return null; this.rootObject = this.getRootNode(aObject) || aObject; // Get or create all of the boxes for the target and its ancestors let objectBox = this.createObjectBoxes(aObject, this.rootObject); if (!objectBox) return null; if (aObject == this.rootObject) return objectBox; return this.populateChildBox(aObject, objectBox.parentNode); }, /** * Creates all of the boxes for an object, its ancestors, and siblings up to * a root. * @param aObject * The tree's object node to create the object boxes for. * @param aRootObject * The root object at which to stop building object boxes. * @returns an object box or null */ createObjectBoxes: function IOBox_createObjectBoxes(aObject, aRootObject) { if (!aObject) return null; if (aObject == aRootObject) { if (!this.rootObjectBox || this.rootObjectBox.repObject != aRootObject) { if (this.rootObjectBox) { try { this.box.removeChild(this.rootObjectBox); } catch (exc) { InspectorUI._log("this.box.removeChild(this.rootObjectBox) FAILS " + this.box + " must not contain " + this.rootObjectBox); } } this.highlightedObjectBox = null; this.selectedObjectBox = null; this.rootObjectBox = this.view.createObjectBox(aObject, true); this.box.appendChild(this.rootObjectBox); } return this.rootObjectBox; } let parentNode = this.view.getParentObject(aObject); let parentObjectBox = this.createObjectBoxes(parentNode, aRootObject); if (!parentObjectBox) return null; let parentChildBox = this.getChildObjectBox(parentObjectBox); if (!parentChildBox) return null; let childObjectBox = this.findChildObjectBox(parentChildBox, aObject); return childObjectBox ? childObjectBox : this.populateChildBox(aObject, parentChildBox); }, /** * Locate the object box for a given object node. * @param aObject * The given object node in the tree. * @returns an object box or null. */ findObjectBox: function IOBox_findObjectBox(aObject) { if (!aObject) return null; if (aObject == this.rootObject) return this.rootObjectBox; let parentNode = this.view.getParentObject(aObject); let parentObjectBox = this.findObjectBox(parentNode); if (!parentObjectBox) return null; let parentChildBox = this.getChildObjectBox(parentObjectBox); if (!parentChildBox) return null; return this.findChildObjectBox(parentChildBox, aObject); }, getAncestorByClass: function IOBox_getAncestorByClass(node, className) { for (let parent = node; parent; parent = parent.parentNode) { if (this.view.hasClass(parent, className)) return parent; } return null; }, /** * We want all children of the parent of repObject. */ populateChildBox: function IOBox_populateChildBox(repObject, nodeChildBox) { if (!repObject) return null; let parentObjectBox = this.getAncestorByClass(nodeChildBox, "nodeBox"); if (parentObjectBox.populated) return this.findChildObjectBox(nodeChildBox, repObject); let lastSiblingBox = this.getChildObjectBox(nodeChildBox); let siblingBox = nodeChildBox.firstChild; let targetBox = null; let view = this.view; let targetSibling = null; let parentNode = view.getParentObject(repObject); for (let i = 0; 1; ++i) { targetSibling = view.getChildObject(parentNode, i, targetSibling); if (!targetSibling) break; // Check if we need to start appending, or continue to insert before if (lastSiblingBox && lastSiblingBox.repObject == targetSibling) lastSiblingBox = null; if (!siblingBox || siblingBox.repObject != targetSibling) { let newBox = view.createObjectBox(targetSibling); if (newBox) { if (lastSiblingBox) nodeChildBox.insertBefore(newBox, lastSiblingBox); else nodeChildBox.appendChild(newBox); } siblingBox = newBox; } if (targetSibling == repObject) targetBox = siblingBox; if (siblingBox && siblingBox.repObject == targetSibling) siblingBox = siblingBox.nextSibling; } if (targetBox) parentObjectBox.populated = true; return targetBox; }, /** * Get the parent object box of a given object box. * @params aObjectBox * The object box of the parent. * @returns an object box or null */ getParentObjectBox: function IOBox_getParentObjectBox(aObjectBox) { let parent = aObjectBox.parentNode ? aObjectBox.parentNode.parentNode : null; return parent && parent.repObject ? parent : null; }, /** * Get the child object box of a given object box. * @param aObjectBox * The object box whose child you want. * @returns an object box or null */ getChildObjectBox: function IOBox_getChildObjectBox(aObjectBox) { return aObjectBox.querySelector(".nodeChildBox"); }, /** * Find the child object box for a given repObject within the subtree * rooted at aParentNodeBox. * @param aParentNodeBox * root of the subtree in which to search for repObject. * @param aRepObject * The object you wish to locate in the subtree. * @returns an object box or null */ findChildObjectBox: function IOBox_findChildObjectBox(aParentNodeBox, aRepObject) { let childBox = aParentNodeBox.firstChild; while (childBox) { if (childBox.repObject == aRepObject) return childBox; childBox = childBox.nextSibling; } return null; // not found }, /** * Determines if the given node is an ancestor of the current root. * @param aNode * The node to look for within the tree. * @returns boolean */ isInExistingRoot: function IOBox_isInExistingRoot(aNode) { let parentNode = aNode; while (parentNode && parentNode != this.rootObject) { parentNode = this.view.getParentObject(parentNode); } return parentNode == this.rootObject; }, /** * Get the root node of a given node. * @param aNode * The node whose root you wish to retrieve. * @returns a root node or null */ getRootNode: function IOBox_getRootNode(aNode) { let node = aNode; let tmpNode; while ((tmpNode = this.view.getParentObject(node))) node = tmpNode; return node; }, };