Last active
October 10, 2017 21:48
-
-
Save leefsmp/af38a1477a74ee8407eff4d2a37a0f71 to your computer and use it in GitHub Desktop.
A patch for Autodesk.Viewing.Extensions.ViewerModelStructurePanel to support multi-models. Just include the script it will override required viewer prototypes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* TreeOnDemand view control | |
* @constructor | |
* @param {TreeDelegate} treeDelegate | |
* @param {Object} root - A node in the model Document | |
* @param {HTMLElement} parentContainer - DOM element parent of the tree. | |
* @param {Object} options | |
*/ | |
function TreeOnDemand(treeDelegate, root, parentContainer, options) { | |
this.treeDelegate = treeDelegate; | |
this.root = root; | |
this.options = options || {}; | |
this.dirty = false; | |
// Initialize root container. | |
this.rootContainer = document.createElement('div'); | |
this.rootContainer.classList.add('treeview'); | |
this.rootContainer.classList.add('on-demand'); | |
this.rootContainer.scroller = document.createElement('div'); | |
this.rootContainer.scroller.classList.add('scroller'); | |
this.rootContainer.appendChild( this.rootContainer.scroller); | |
if (parentContainer) { | |
parentContainer.appendChild(this.rootContainer); | |
} | |
// Initialize tables. | |
// These are tables to share css strings between nodes. | |
var nodeCssTable = [[], ['group'], ['leaf']]; | |
var nodeIndexToNodeCssTable = new Uint16Array(treeDelegate.getTreeNodeCount()); | |
var cssStringToNodeCssTable = {'': 0, 'group': 1, 'leaf': 2}; | |
var createTables = function(node) { | |
var nodeId = getNodeId(this, node); | |
var nodeIndex = treeDelegate.getTreeNodeIndex(nodeId); | |
nodeIndexToNodeCssTable[nodeIndex] = treeDelegate.isTreeNodeGroup(node) ? 1 : 2; | |
this.treeDelegate.forEachChild(node, createTables); | |
return true; | |
}.bind(this); | |
createTables(this.root); | |
this.nodeCssTable = nodeCssTable; | |
this.nodeIndexToNodeCssTable = nodeIndexToNodeCssTable; | |
this.cssStringToNodeCssTable = cssStringToNodeCssTable; | |
// Creates element pools. | |
var elementsPool = []; | |
var elementsPoolCount = 150; //ELEMENT_POOL_LENGHT | |
var elementsUsedCount = 0; | |
for (var i = 0; i < elementsPoolCount; ++i) { | |
var element = createNodeHTmlElement(); | |
elementsPool[i] = element; | |
} | |
this.elementsPool = elementsPool; | |
this.elementsUsed = 0; | |
// Add input event listeners. | |
var touchDevice = Autodesk.Viewing.isTouchDevice(); | |
if (touchDevice) { | |
this.hammer = new Hammer.Manager(this.rootContainer, { | |
recognizers: [ | |
Autodesk.Viewing.GestureRecognizers.doubletap, | |
Autodesk.Viewing.GestureRecognizers.press | |
], | |
inputClass: Hammer.TouchInput | |
}); | |
} | |
this.scrollListener = onPanelScroll.bind(this); | |
parentContainer.addEventListener('scroll', this.scrollListener); | |
var _this = this; | |
this.removeListeners = function() { | |
parentContainer.removeEventListener('scroll', _this.scrollListener); | |
} | |
for (var i = 0; i < elementsPoolCount; ++i) { | |
var element = elementsPool[i]; | |
if (touchDevice) { | |
this.hammer.on('doubletap', onElementDoubleTap.bind(this)); | |
this.hammer.on('press', onElementPress.bind(this)); | |
} | |
element.addEventListener('click', onElementClick.bind(this)); | |
element.addEventListener('dblclick', onElementDoubleClick.bind(this)); | |
element.addEventListener('contextmenu', onElementContextMenu.bind(this)); | |
element.addEventListener('mouseover', onElementMouseOver.bind(this)); | |
element.addEventListener('mouseout', onElementMouseOut.bind(this)); | |
element.icon.addEventListener('click', onElementIconClick.bind(this)); | |
} | |
redraw(this); | |
}; | |
var proto = TreeOnDemand.prototype; | |
proto.constructor = TreeOnDemand; | |
/** | |
* Show/hide the tree control | |
* @param {boolean} show - true to show the tree control, false to hide it | |
*/ | |
proto.show = function (show) { | |
this.rootContainer.style.display = 'show' ? block : 'none'; | |
}; | |
/** | |
* Get the root container | |
* @nosideeffects | |
* @returns {string} | |
*/ | |
proto.getRootContainer = function () { | |
return this.rootContainer; | |
}; | |
/** | |
* Get the tree delegate | |
* @nosideeffects | |
* @returns {TreeDelegate} | |
*/ | |
proto.delegate = function () { | |
return this.treeDelegate; | |
}; | |
/** | |
* Is the given group node in the tree collapsed? | |
* @nosideeffects | |
* @param {Object} group -The group node | |
* @returns {boolean} true if group node is collapsed, false if expanded | |
*/ | |
proto.isCollapsed = function(group) { | |
var css = getNodeCss(this, group); | |
return css && css.indexOf('collapsed') !== -1; | |
}; | |
/** | |
* Collapse/expand the given group node in the tree | |
* @param {Object} group - the group node | |
* @param {boolean} collapsed - true to collapse the group node, false to expand it | |
*/ | |
proto.setCollapsed = function(group, collapsed, recursive) { | |
if (collapsed) { | |
this.addClass(group, 'collapsed', recursive); | |
this.removeClass(group, 'expanded', recursive); | |
} else { | |
this.addClass(group, 'expanded', recursive); | |
this.removeClass(group, 'collapsed', recursive); | |
} | |
}; | |
/** | |
* Collapse/expand all group nodes in the tree | |
* @param {boolean} collapsed - true to collapse tree, false to expand it | |
*/ | |
proto.setAllCollapsed = function(collapsed) { | |
var collapse = collapsed ? | |
function(node) { | |
this.addClass(node, 'collapsed', false); | |
this.removeClass(node, 'expanded', false); | |
}.bind(this) : | |
function(node) { | |
this.addClass(node, 'collapsed', false); | |
this.removeClass(node, 'expanded', false); | |
}.bind(this); | |
this.iterate(this.root,function(node) { | |
this.treeDelegate.isTreeNodeGroup(node) && collapse(node); | |
return true; | |
}.bind(this)); | |
}; | |
/** | |
* Add the given nodes to the current selection | |
* @param {Array.<Object>} nodes - nodes to add to the current selection | |
*/ | |
proto.addToSelection = function(nodes) { | |
var nodesCount = nodes.length; | |
for (var i = 0; i < nodesCount; ++i) { | |
this.addClass(nodes[i], 'selected', false); | |
} | |
redraw(this); | |
}; | |
/** | |
* Remove the given nodes from the current selection | |
* @param {Array.<Object>} nodes - The nodes to remove from the current selection | |
*/ | |
proto.removeFromSelection = function(nodes) { | |
var nodesCount = nodes.length; | |
for (var i = 0; i < nodesCount; ++i) { | |
this.removeClass(nodes[i], 'selected', false); | |
} | |
redraw(this); | |
}; | |
/** | |
* Set the current selection | |
* @param {Array.<Object>} nodes - nodes to make currently selected | |
*/ | |
proto.setSelection = function(nodes) { | |
this.clearSelection(); | |
this.addToSelection(nodes); | |
return this.selectedNodes; | |
}; | |
/** | |
* Clear the current selection | |
*/ | |
proto.clearSelection = function () { | |
// In order to optimize memmory, we send one at a time (the whole model could be selected). | |
var nodeToRemove = []; | |
var unselect = function(node) { | |
var nodeId = getNodeId(this, node); | |
var css = getNodeCss(this, nodeId); | |
if (css.indexOf('selected') !== -1) { | |
nodeToRemove[0] = nodeId; | |
this.removeFromSelection(nodeToRemove); | |
} | |
this.treeDelegate.forEachChild(node, unselect); | |
return true; | |
}.bind(this); | |
unselect(this.root); | |
}; | |
/** | |
* Is the given node selected? | |
* @nosideeffects | |
* @param {Object} node - The tree node | |
* @returns {boolean} - true if node is selected, false otherwise | |
*/ | |
proto.isSelected = function(node) { | |
return getNodeCss(this, node).indexOf('selected') !== -1; | |
}; | |
/** | |
* Scrolls the tree control so the node provided shows at the top. | |
* @param {Object} node - The tree node | |
*/ | |
proto.scrollTo = function(node) { | |
var nodeFound = false; | |
var nodeId = getNodeId(this, node); | |
var expandedHeightStack = []; // Heights of the visible branches and nodes before node. | |
var getNodeScrollTop = function(candidate) { | |
var candidateId = getNodeId(this, candidate); | |
nodeFound = nodeFound || nodeId === candidateId; | |
if (nodeFound) { | |
return; | |
} | |
expandedHeightStack.push(this.treeDelegate.getTreeNodeClientHeight(node)); | |
var stackSize = expandedHeightStack.length; | |
var elementExpanded = ( | |
this.treeDelegate.isTreeNodeGroup(candidateId) && | |
getNodeCss(this, candidateId).indexOf('expanded') !== -1); | |
this.treeDelegate.forEachChild(candidate, getNodeScrollTop); | |
if(!elementExpanded && !nodeFound) { | |
if (expandedHeightStack.length > stackSize) { | |
expandedHeightStack.splice(stackSize); | |
} | |
} | |
}.bind(this); | |
this.setCollapsed(node, false, true); | |
// Calculate and set the container's parent scroll top. | |
getNodeScrollTop(this.root); | |
if (!nodeFound) { | |
return; | |
} | |
var scrollTop = 0; | |
var expandedHeightStackCount = expandedHeightStack.length; | |
for (var i = this.options.excludeRoot ? 1 : 0; i < expandedHeightStackCount; ++i) { | |
scrollTop += expandedHeightStack[i]; | |
} | |
if (this.rootContainer.parentNode.scrollTop === scrollTop) { | |
return; | |
} | |
// If the panel will grow due to breanch expansion, | |
// it's needed to regenerate the panel and then scroll to the deisred item. | |
redraw(this, true); | |
this.rootContainer.parentNode.scrollTop = scrollTop; | |
}; | |
/** | |
* Add a class to a node | |
* @param {Number|Object} node - The tree node | |
* @param {string} className | |
* @returns {boolean} - true if the class was added, false otherwise | |
*/ | |
proto.addClass = function(node, className, recursive) { | |
function add(tree, nodeId, className) { | |
var css = getNodeCss(tree, nodeId); | |
var cssIndex = css.indexOf(className); | |
if (cssIndex !== -1) { | |
return; | |
} | |
css = css.slice(0); | |
css.push(className); | |
css.sort(); | |
setNodeCss(tree, nodeId, css); | |
} | |
// It is intentional that the recursive add starts at the parent. | |
var delegate = this.treeDelegate; | |
if (recursive) { | |
var parentId = delegate.getTreeNodeParentId(getNodeId(this, node)); | |
while (parentId) { | |
add(this, parentId, className); | |
parentId = delegate.getTreeNodeParentId(parentId); | |
} | |
} else { | |
add(this, node, className); | |
} | |
redraw(this); | |
return true; | |
}; | |
/** | |
* Remove a class from a node | |
* @param {Number|Object} node - The tree node or its dbId | |
* @param {string} className | |
* @returns {boolean} - true if the class was removed, false otherwise | |
*/ | |
proto.removeClass = function (node, className, recursive) { | |
function remove(tree, nodeId, className) { | |
var css = getNodeCss(tree, nodeId); | |
var cssIndex = css.indexOf(className); | |
if (cssIndex === -1) { | |
return; | |
} | |
css = css.slice(0); | |
css.splice(cssIndex, 1); | |
setNodeCss(tree, nodeId, css); | |
}; | |
//It is intentional that the recursive add starts at the parent. | |
var delegate = this.treeDelegate; | |
if (recursive) { | |
var parentId = delegate.getTreeNodeParentId(getNodeId(this, node)); | |
while (parentId) { | |
remove(this, parentId, className); | |
parentId = delegate.getTreeNodeParentId(parentId); | |
} | |
} else { | |
remove(this, node, className); | |
} | |
redraw(this); | |
return true; | |
}; | |
/** | |
* Does the node have the given class? | |
* @nosideeffects | |
* @param {Number|Object} node - The node or its dbId | |
* @param {string} className | |
* @returns {boolean} true if the node has the given class, false otherwise | |
*/ | |
proto.hasClass = function(node, className) { | |
return getNodeCss(this, node).indexOf(className) !== 1; | |
}; | |
/** | |
* Clears the contents of the tree | |
*/ | |
proto.clear = function() { | |
var scroller = this.rootContainer.scroller; | |
var child; | |
while (child = scroller.lastChild) { | |
scroller.removeChild(child); | |
} | |
this.elementsUsed = 0; | |
}; | |
/** | |
* Iterates through nodes in the tree in pre-order. | |
* @param {Object|Number} node - node at which to start the iteration. | |
* @param {function(Object)} callback - callback function for each iterated node, if callbak returns false, node's chidren are not visited. | |
*/ | |
proto.iterate = function(node, callback) { | |
// roodId === 0 is a valid root node | |
if (node === undefined || node === null) { | |
return; | |
} | |
if(!this.treeDelegate.shouldCreateTreeNode(node)) { | |
return; | |
} | |
if(!callback(node)) { | |
return; | |
} | |
this.treeDelegate.forEachChild(node, function(child) { | |
this.iterate(child, callback); | |
}.bind(this)); | |
}; | |
/** | |
* | |
* @param {*} tree | |
* @param {*} initial | |
*/ | |
function redraw(tree, inmediate) { | |
var clearElementTree = function() { | |
var elementsUsed = tree.elementsUsed; | |
var elementsPool = tree.elementsPool; | |
// Return used elements to the elements pool. | |
for (var i = 0; i < elementsUsed; ++i) { | |
// Remove node id, just in case. | |
var element = elementsPool[i]; | |
element.setAttribute('lmv-nodeId', ''); | |
// Remove css classes. | |
element.className = ''; | |
// Remove all controls and listeners added by tree delegate, we spare the icon. | |
var header = element.header; | |
var childrenToRemove = header.childNodes.length - 1; | |
for (var j = 0; j < childrenToRemove; ++j) { | |
header.removeChild(header.lastChild); | |
} | |
} | |
tree.clear(); | |
}.bind(tree); | |
var createTreeElements = function(node) { | |
var container = tree.rootContainer; | |
var scroller = container.scroller; | |
var nodeId = getNodeId(tree, node); | |
if (tree.elementsUsed === tree.elementsPool.length) { | |
return false; | |
} | |
// Add size to container panel. | |
var elementClasses = getNodeCss(tree, nodeId); | |
var elementHeight = tree.nodeDepth === -1 ? 0 : tree.treeDelegate.getTreeNodeClientHeight(node); | |
var elementTop = container.height; | |
var elementBottom = elementTop + elementHeight; | |
container.height = elementBottom; | |
// if node is inside scroll window, create it. | |
if (elementBottom < scroller.top || elementTop > scroller.top + scroller.height) { | |
// Add children if this is an expanded group. | |
if (elementClasses.indexOf('expanded') !== -1) { | |
tree.nodeDepth++; | |
tree.treeDelegate.forEachChild(node, createTreeElements); | |
tree.nodeDepth--; | |
} | |
return; | |
} | |
// Adjust scroller position to align with partialy in view first element. | |
if (scroller.empty) { | |
scroller.empty = false; | |
scroller.style.top = (scroller.top - (elementHeight - (container.height - scroller.top))) + 'px'; | |
} | |
// Add element to panel. | |
var element = null; | |
if (elementHeight > 0) { | |
element = tree.elementsPool[tree.elementsUsed++]; | |
element.setAttribute("lmv-nodeId", nodeId); | |
tree.treeDelegate.createTreeNode(node, element.header, tree.options); | |
var css = tree.treeDelegate.getTreeNodeClass(node); | |
if (css) { | |
element.classList.add(css); | |
} | |
var elementClassesCount = elementClasses.length; | |
for (var i = 0; i < elementClassesCount; ++i) { | |
element.classList.add(elementClasses[i]); | |
} | |
var offset = tree.treeDelegate.getTreeNodeDepthOffset(node, tree.nodeDepth); | |
element.style.left = offset + 'px'; | |
scroller.appendChild(element); | |
} | |
// Add children if tree is an expanded group. | |
if (elementClasses.indexOf('expanded') !== -1) { | |
tree.nodeDepth++; | |
tree.treeDelegate.forEachChild(node, createTreeElements); | |
tree.nodeDepth--; | |
} | |
}.bind(tree); | |
var createElementTree = function() { | |
tree.dirty = false; | |
tree.nodeDepth = tree.options.excludeRoot ? -1 : 0;; | |
// Set scroller size to match parent of root container. | |
var container = tree.rootContainer; | |
container.height = 0; | |
var containerParent = container.parentNode; | |
if(!containerParent) { | |
return; | |
} | |
var scroller = container.scroller; | |
var parentSize = tree.treeDelegate.getTreeNodeParentMaxSize(); | |
var SCROLL_SAFE_PADDING = 300; | |
scroller.empty = true; | |
scroller.top = Math.max(0, containerParent.scrollTop - SCROLL_SAFE_PADDING), tree.rootContainer.height; | |
scroller.width = 999999; | |
scroller.height = parentSize.height + 2 * SCROLL_SAFE_PADDING; | |
scroller.style.width = scroller.width + 'px'; | |
scroller.style.top = scroller.top + 'px'; | |
clearElementTree(); | |
createTreeElements(tree.root); | |
// Shrink scroller actual height if needed, so control doesn't go outside of the container. | |
var height = Math.max(0, Math.min(container.height, scroller.top + scroller.height) - scroller.top); | |
if (height !== scroller.height) { | |
scroller.style.height = height + 'px'; | |
} | |
var top = Math.min(scroller.top, tree.rootContainer.height); | |
if (top !== scroller.top) { | |
scroller.style.top = top + 'px'; | |
} | |
var newContainerHeight = tree.rootContainer.height + 'px'; | |
if (container.style.height != newContainerHeight) { | |
container.style.height = newContainerHeight; | |
tree.treeDelegate.onTreeNodeReized(tree); | |
} | |
}.bind(tree); | |
// If the panel is not dirty, marked as dirty and schedule an update during next frame. | |
if (tree.dirty && !inmediate) { | |
return false; | |
} | |
if (inmediate) { | |
createElementTree(); | |
} else { | |
tree.dirty = true; | |
// All update requests are executed as one during next frame. | |
requestAnimationFrame(function() { | |
createElementTree(); | |
}); | |
} | |
} | |
/** | |
* Get the id of the node if it's an object or returns input parameter if it's string or number. | |
* @private | |
* @param {*} node - A node object or a string or number with the id of the node. | |
* @returns {number} The id of the node | |
*/ | |
function getNodeId(tree, node) { | |
if (typeof node !== "number" && typeof node !== "string") { | |
return tree.threeDelegate.getTreeNodeId(node | 0x0); | |
} | |
return node; | |
} | |
/** | |
* Returns the node associated to the html element provided | |
* @private | |
* @param {*} tree - A TreeOnDemand object instance. | |
* @param {*} element - A node object or a string or number with the id of the node. | |
* @returns {Number} Node object associated with with the html control. | |
*/ | |
function getNodeIdFromElement(tree, element) { | |
var nodeElement = null; | |
while (element && element !== tree.rootContainer) { | |
if (element.hasAttribute("lmv-nodeId")) { | |
nodeElement = element; | |
break; | |
} | |
element = element.parentElement; | |
} | |
if(!nodeElement) { | |
return null; | |
} | |
var nodeId = nodeElement.getAttribute("lmv-nodeId"); | |
return nodeId | 0; | |
}; | |
/** | |
* Get the css array from the css table. | |
* @private | |
* @param {*} tree - A TreeOnDemand object instance. | |
* @param {Number} nodeId - A node id to whome state will be retrived. | |
* @returns {Array} Array of strings with the css classes | |
*/ | |
function getNodeCss(tree, node) { | |
var nodeIndex = tree.treeDelegate.getTreeNodeIndex(node); | |
return tree.nodeCssTable[tree.nodeIndexToNodeCssTable[nodeIndex]]; | |
} | |
/** | |
* Adds a new css entry table is needed and associate the css table index to node. | |
* @private | |
* @param {*} tree - A TreeOnDemand object instance. | |
* @param {Number} nodeId - A node id to whome state will be retrived. | |
*/ | |
function setNodeCss(tree, node, css) { | |
var key = css.join(' '); | |
var index = tree.cssStringToNodeCssTable[key] || tree.nodeCssTable.length; | |
if (index === tree.nodeCssTable.length) { | |
tree.nodeCssTable.push(css); | |
tree.cssStringToNodeCssTable[key] = index; | |
} | |
var nodeIndex = tree.treeDelegate.getTreeNodeIndex(node); | |
tree.nodeIndexToNodeCssTable[nodeIndex] = index; | |
} | |
/** | |
* Given a node, create the corresponding HTML elements for the node and all of its descendants | |
* @private | |
* @param {Object} tree - TreeOnDemand node | |
* @param {Object=} [options] - An optional dictionary of options. Current parameters: | |
* {boolean} [localize] - when true, localization is attempted for the given node; false by default. | |
* @param {Number} [depth] | |
*/ | |
function createNodeHTmlElement(tree, options) { | |
var header = document.createElement('lmvheader'); | |
var icon = document.createElement('icon'); | |
header.appendChild(icon); | |
var element = document.createElement('div'); | |
element.header = header; | |
element.icon = icon; | |
element.appendChild(header); | |
return element; | |
}; | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onPanelScroll(event) { | |
var container = this.rootContainer; | |
var scroller = container.scroller; | |
var parentSize = this.treeDelegate.getTreeNodeParentMaxSize(); | |
// If scroller is still inside the container visible area, return, no need to recalculate. | |
if (scroller.top + scroller.height < container.parentNode.scrollTop + parentSize.height || | |
scroller.top > container.parentNode.scrollTop) { | |
redraw(this, true); | |
} | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementDoubleTap(event) { | |
var nodeId = getNodeIdFromElement(this, event.target); | |
nodeId && this.treeDelegate.onTreeNodeDoubleClick(this, nodeId, event); | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementPress(event) { | |
var nodeId = getNodeIdFromElement(this, event.target); | |
nodeId && this.treeDelegate.onTreeNodeRightClick(this, nodeId, event); | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementClick(event) { | |
// Click has to be done over the children of the tree elements. | |
// Group and leaf nodes are only containers to layout consumer content. | |
if (event.target.classList.contains('group') || | |
event.target.classList.contains('leaf')) { | |
return; | |
} | |
var nodeId = getNodeIdFromElement(this, event.target); | |
if(!nodeId) { | |
return; | |
} | |
this.treeDelegate.onTreeNodeClick(this, nodeId, event); | |
event.stopPropagation(); | |
if(!event.target.classList.contains('propertyLink')) { | |
event.preventDefault(); | |
} | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementDoubleClick(event) { | |
// Click has to be done over the children of the tree elements. | |
// Group and leaf nodes are only containers to layout consumer content. | |
if (event.target.classList.contains('group') || | |
event.target.classList.contains('leaf')) { | |
return; | |
} | |
var nodeId = getNodeIdFromElement(this, event.target); | |
if(!nodeId) { | |
return; | |
} | |
this.treeDelegate.onTreeNodeDoubleClick(this, nodeId, event); | |
event.stopPropagation(); | |
event.preventDefault(); | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementContextMenu(event) { | |
// Click has to be done over the children of the tree elements. | |
// Group and leaf nodes are only containers to layout consumer content. | |
if (event.target.classList.contains('group') || | |
event.target.classList.contains('leaf')) { | |
return; | |
} | |
var nodeId = getNodeIdFromElement(this, event.target); | |
if(!nodeId) { | |
return; | |
} | |
this.treeDelegate.onTreeNodeRightClick(this, nodeId, event); | |
event.stopPropagation(); | |
event.preventDefault(); | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementMouseOver(event) { | |
// Hover has to be done over the children of the tree elements. | |
// Group and leaf nodes are only containers to layout consumer content. | |
if (event.target.classList.contains('group') || | |
event.target.classList.contains('leaf')) { | |
this.treeDelegate.onTreeNodeHover(this, -1, event); | |
return; | |
} | |
var nodeId = getNodeIdFromElement(this, event.target); | |
if(!nodeId) { | |
return; | |
} | |
this.treeDelegate.onTreeNodeHover(this, nodeId, event); | |
event.stopPropagation(); | |
event.preventDefault(); | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementMouseOut(event) { | |
// When the mouse leaves the element, set node to -1 (background), no highlight, | |
// If the mouse out event is within the same element. don't do anything. | |
var element = event.toElement || event.relatedTarget; | |
if (getNodeIdFromElement(this, event.target) !== getNodeIdFromElement(this, element)) { | |
this.treeDelegate.onTreeNodeHover(this, -1, event); | |
event.stopPropagation(); | |
event.preventDefault(); | |
} | |
} | |
/** | |
* | |
* @param {*} event | |
*/ | |
function onElementIconClick(event) { | |
var nodeId = getNodeIdFromElement(this, event.target); | |
if(!nodeId) { | |
return; | |
} | |
this.treeDelegate.onTreeNodeIconClick(this, nodeId, event); | |
event.stopPropagation(); | |
event.preventDefault(); | |
} | |
/** | |
* Model structure panel. | |
* | |
* Sets the model structure panel for displaying the loaded model. | |
* @alias Autodesk.Viewing.UI.ModelStructurePanel | |
* @augments Autodesk.Viewing.UI.DockingPanel | |
* @param {HTMLElement} parentContainer - The container for this panel. | |
* @param {string} id - The id for this panel. | |
* @param {string} title - The initial title for this panel. | |
* @param {object} [options] - An optional dictionary of options. | |
* @param {boolean} [options.startCollapsed=true] - When true, collapses all of the nodes under the root. | |
* @constructor | |
* @category UI | |
*/ | |
function ModelStructurePanel(parentContainer, id, title, options) | |
{ | |
Autodesk.Viewing.UI.DockingPanel.call(this, parentContainer, id, title, options); | |
this.container.classList.add('modelStructurePanel'); | |
options = options || {}; | |
if (!options.heightAdjustment) | |
options.heightAdjustment = 40; | |
if (!options.marginTop) | |
options.marginTop = 0; | |
options.left = true; | |
this.createScrollContainer(options); | |
this.options = options; | |
this.instanceTree = null; | |
this.tree = null; | |
this.selectedNodes = []; | |
this.uiCreated = false; | |
var that = this; | |
this.addVisibilityListener(function (show) { | |
if (show) { | |
if (!that.uiCreated) { | |
that.createUI(); | |
} | |
that.resizeToContent(); | |
} | |
}); | |
}; | |
ModelStructurePanel.prototype = Object.create(Autodesk.Viewing.UI.DockingPanel.prototype); | |
ModelStructurePanel.prototype.constructor = ModelStructurePanel; | |
/** | |
* Sets the model for this panel to display. | |
* | |
* @param {InstanceTree} instanceTree - The object tree returned by Autodesk.Viewing.Model.getObjectTree( function(instanceTree) ); | |
* @param {string=} [modelTitle] - An optional title to display for this model. | |
*/ | |
ModelStructurePanel.prototype.setModel = function(instanceTree, modelTitle) | |
{ | |
this.instanceTree = instanceTree; | |
this.modelTitle = modelTitle; | |
if (this.isVisible()) | |
this.createUI(); | |
else | |
this.uiCreated = false; | |
}; | |
/** | |
* Used for delayed initialization of the HTML DOM tree | |
* @private | |
*/ | |
ModelStructurePanel.prototype.createUI = function() | |
{ | |
var that = this; | |
var instanceTree = that.instanceTree; | |
if (!instanceTree /*|| this.uiCreated*/) | |
return; | |
var createDelegate = function() | |
{ | |
var delegate = new Autodesk.Viewing.UI.TreeDelegate(); | |
delegate.getTreeNodeId = function(node) | |
{ | |
if (typeof node == "object") { | |
Autodesk.Viewing.Private.logger.warn("Object used instead of dbId. Fix it."); | |
return node.dbId; | |
} else | |
return node; | |
}; | |
delegate.getTreeNodeIndex = function(node) | |
{ | |
if (typeof node == "object") { | |
Autodesk.Viewing.Private.logger.warn("Object used instead of dbId. Fix it."); | |
return that.instanceTree.nodeAccess.dbIdToIndex[node.dbId]; | |
} else | |
return that.instanceTree.nodeAccess.dbIdToIndex[node]; | |
}; | |
delegate.getTreeNodeLabel = function(node) | |
{ | |
var dbId = this.getTreeNodeId(node); | |
var res = that.instanceTree.getNodeName(dbId); | |
if (dbId == -1e10) // Replace Object -10000000000 with Object 0 | |
res = 'Object 0'; | |
return res || ('Object ' + dbId); | |
}; | |
delegate.getTreeNodeClass = function(node) | |
{ | |
return that.getNodeClass(node); | |
}; | |
delegate.getTreeNodeParentId = function(nodeId) | |
{ | |
return that.instanceTree.nodeAccess.getParentId(nodeId); | |
} | |
delegate.getTreeNodeCount = function() | |
{ | |
return that.instanceTree.nodeAccess.getNumNodes(); | |
} | |
delegate.getTreeNodeParentMaxSize = function() | |
{ | |
var width = that.container.clientWidth | 0x0; | |
var height = that.container.style.maxHeight.replace('px', '') | 0x0; | |
return {width: width, height: height}; | |
} | |
delegate.getTreeNodeClientHeight = function (node) | |
{ | |
return 29; | |
} | |
delegate.getTreeNodeDepthOffset = function (node, depth) | |
{ | |
return 25 * depth; | |
} | |
delegate.isTreeNodeGroup = function(node) | |
{ | |
return that.isGroupNode(node); | |
}; | |
delegate.shouldCreateTreeNode = function(node) | |
{ | |
return that.shouldInclude(node); | |
}; | |
delegate.onTreeNodeClick = function(tree, node, event) | |
{ | |
that.onClick(node, event); | |
}; | |
delegate.onTreeNodeRightClick = function(tree, node, event) | |
{ | |
that.onRightClick(node, event); | |
}; | |
delegate.onTreeNodeDoubleClick = function(tree, node, event) | |
{ | |
that.onDoubleClick(node, event); | |
}; | |
delegate.onTreeNodeIconClick = function(tree, node, event) | |
{ | |
that.onIconClick(node, event); | |
}; | |
delegate.onTreeNodeReized = function(tree) | |
{ | |
that.resizeToContent(); | |
}; | |
delegate.forEachChild = function(node, callback) | |
{ | |
var dbId = this.getTreeNodeId(node); | |
that.instanceTree.enumNodeChildren(dbId, callback); | |
}; | |
delegate.onTreeNodeHover = function(tree, node, event) | |
{ | |
that.onHover(node, event); | |
}; | |
return delegate; | |
}; | |
var title = that.modelTitle; | |
if(!title) { | |
if (that.options && that.options.defaultTitle) { | |
title = that.options.defaultTitle; | |
} | |
} | |
var options = {}; | |
if(!title) { | |
title = "Browser"; // localized by DockingPanel.prototype.setTitle | |
options.localizeTitle = true; | |
} | |
that.setTitle(title, options); | |
// Remove the previous tree from the scroll container and any listeners on the title bar. | |
// | |
if(that.tree) { | |
while(that.scrollContainer.hasChildNodes()){ | |
that.scrollContainer.removeChild(that.scrollContainer.lastChild); | |
} | |
that.title.removeEventListener("click", that.onTitleClick); | |
that.title.removeEventListener("dblclick", that.onTitleDoubleClick); | |
that.tree.removeListeners && that.tree.removeListeners(); | |
} | |
var rootId = this.rootId = instanceTree.getRootId(); | |
var rootName = instanceTree.getNodeName(rootId); | |
var childName; | |
var childId = 0; | |
var childCount = 0; | |
instanceTree.enumNodeChildren(rootId, function(child) { | |
if (!childCount) { | |
childName = instanceTree.getNodeName(child); | |
childId = child; | |
} | |
childCount++; | |
}); | |
var delegate = createDelegate(); | |
this.myDelegate = delegate; | |
//Detect Fusion models which have a root inside a root (which was probably an arms race | |
//against us putting the root in the title bar) | |
var skipRoot = (childCount === 1 && rootName === childName); | |
var treeOptions = { | |
excludeRoot: !!(that.options && that.options.excludeRoot) || skipRoot, | |
startCollapsed: !!(that.options && that.options.startCollapsed) && !skipRoot, | |
localize: true, | |
}; | |
that.tree = new TreeOnDemand(delegate, rootId, that.scrollContainer, treeOptions); | |
// Initialize collapsed states. | |
that.tree.setAllCollapsed(true); | |
if (treeOptions.excludeRoot) { | |
that.tree.setCollapsed(rootId, false); | |
if(!treeOptions.startCollapsed) { | |
that.tree.setCollapsed(childId, false); | |
} | |
} else { | |
if(!treeOptions.startCollapsed) { | |
that.tree.setCollapsed(rootId, false); | |
} | |
} | |
this.uiCreated = true; | |
}; | |
/** | |
* Override this method to specify the label for a node. | |
* By default, this is the node's name, or 'Object ' + object id if the name | |
* is blank. | |
* | |
* @param {Object} node - A node in an Autodesk.Viewing.Model | |
* @returns {string} Label of the tree node | |
*/ | |
ModelStructurePanel.prototype.getNodeLabel = function(node) | |
{ | |
return this.myDelegate.getNodeLabel(node); | |
}; | |
/** | |
* Override this to specify the CSS classes of a node. This way, in CSS, the designer | |
* can specify custom styling per type. | |
* By default, an empty string is returned. | |
* | |
* @param {Object} node - A node in an Autodesk.Viewing.Model | |
* @returns {string} CSS classes for the node | |
*/ | |
ModelStructurePanel.prototype.getNodeClass = function(node) | |
{ | |
return ''; | |
}; | |
/** | |
* Override this method to specify whether or not a node is a group node. | |
* By default, a node is considered a group if it has a 'children' property containing | |
* an array with at least one element. | |
* | |
* @param {Object} node - A node in an Autodesk.Viewing.Model | |
* @returns {boolean} true if this node is a group node, false otherwise | |
*/ | |
ModelStructurePanel.prototype.isGroupNode = function(node) | |
{ | |
var dbId = this.myDelegate.getTreeNodeId(node); | |
return this.instanceTree.getChildCount(dbId); | |
}; | |
/** | |
* Override this method to specify if a tree node should be created for this node. | |
* By default, every node will be displayed. | |
* | |
* @param {Object} node - A node in an {@link Autodesk.Viewing.Model} | |
* @returns {boolean} true if a node should be created, false otherwise | |
*/ | |
ModelStructurePanel.prototype.shouldInclude = function(node) | |
{ | |
return true; | |
}; | |
/** | |
* Override this method to do something when the user clicks on a tree node | |
* @param {Object} node - A node in an {@link Autodesk.Viewing.Model} | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onClick = function(node, event) | |
{ | |
this.setSelection([node]); | |
}; | |
/** | |
* Override this to do something when the user double-clicks on a tree node | |
* | |
* @param {Object} node - A node in an {@link Autodesk.Viewing.Model} | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onDoubleClick = function(node, event) | |
{ | |
}; | |
/** | |
* Override this to do something when the user clicks on a tree node's icon. | |
* By default, groups will be expanded/collapsed. | |
* | |
* @param {Object} node - A node in an {@link Autodesk.Viewing.Model} | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onIconClick = function(node, event) | |
{ | |
this.setGroupCollapsed(node, !this.isGroupCollapsed(node)); | |
}; | |
/** | |
* Collapse/expand a group node. | |
* | |
* @param {Object} node - A node to collapse/expand in the tree. | |
* @param {Boolean} collapsed - True to collapse the group, false to expand it. | |
*/ | |
ModelStructurePanel.prototype.setGroupCollapsed = function(node, collapsed) | |
{ | |
var delegate = this.tree.delegate(); | |
if (delegate.isTreeNodeGroup(node)) { | |
this.tree.setCollapsed(node, collapsed); | |
} | |
}; | |
/** | |
* Returns true if the group is collapsed. | |
* | |
* @param {Object} node - The node in the tree. | |
* @returns {Boolean} - True if the group is collapsed, false otherwise. | |
*/ | |
ModelStructurePanel.prototype.isGroupCollapsed = function(node) | |
{ | |
var delegate = this.tree.delegate(); | |
if (delegate.isTreeNodeGroup(node)) { | |
return this.tree.isCollapsed(node); | |
} | |
return false | |
}; | |
/** | |
* Override this to do something when the user right-clicks on a tree node | |
* | |
* @param {Object} node - A node in an Autodesk.Viewing.Model | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onRightClick = function(node, event) | |
{ | |
}; | |
/** | |
* Override this method to be notified when the user clicks on the title. | |
* @override | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onTitleClick = function(event) | |
{ | |
}; | |
/** | |
* Override this method to be notified when the user double-clicks on the title. | |
* @override | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onTitleDoubleClick = function(event) | |
{ | |
}; | |
/** | |
* Override this to do something when the user hovers on a tree node | |
* | |
* @param {Object} node - A node in an {@link Autodesk.Viewing.Model} | |
* @param {Event} event | |
*/ | |
ModelStructurePanel.prototype.onHover = function(node, event) | |
{ | |
}; | |
/** | |
* Displays the given nodes as selected in this panel. | |
* | |
* @param {Array} nodes - An array of Autodesk.Viewing.Model nodes to display as selected | |
*/ | |
ModelStructurePanel.prototype.setSelection = function(nodes) | |
{ | |
// Bail if no model structure. | |
// | |
if(!this.tree) { | |
this.selectedNodes = nodes; | |
return; | |
} | |
var i, parent; | |
// Un-mark the ancestors. | |
// | |
for(i=0; i<this.selectedNodes.length; ++i) { | |
parent = this.instanceTree.getNodeParentId(this.selectedNodes[i]); | |
while(parent) { | |
this.tree.removeClass(parent, 'ancestor-selected'); | |
parent = this.instanceTree.getNodeParentId(parent); | |
} | |
} | |
// Mark the ancestors of the newly selected nodes. | |
// | |
var selectedNodesHierarchy = []; | |
for(i=0; i<nodes.length; ++i) { | |
selectedNodesHierarchy.push(nodes[i]); | |
parent = this.instanceTree.getNodeParentId(nodes[i]); | |
while(parent) { | |
this.tree.addClass(parent, 'ancestor-selected'); | |
parent = this.instanceTree.getNodeParentId(parent); | |
} | |
} | |
// Mark the newly selected nodes. | |
// | |
this.selectedNodes = nodes; | |
this.tree.setSelection(selectedNodesHierarchy); | |
}; | |
/** | |
* Returns the width and height to be used when resizing the panel to the content. | |
* | |
* @returns {{height: number, width: number}} | |
*/ | |
ModelStructurePanel.prototype.getContentSize = function () | |
{ | |
var tree = this.tree; | |
if (tree) { | |
var treeContainer = tree.getRootContainer(); | |
if (treeContainer) { | |
return {height: treeContainer.clientHeight + this.options.heightAdjustment + 35, width: treeContainer.clientWidth}; | |
} | |
} | |
return {height: 0, width: 0}; | |
}; | |
/** | |
* Given a node's id, adds the given CSS class to this node. | |
* | |
* @param {string} id - The id of a node in an Autodesk.Viewing.Model | |
* @param {string} className - The CSS class to add | |
* @returns {boolean} - true if the class was added, false otherwise | |
*/ | |
ModelStructurePanel.prototype.addClass = function(id, className) | |
{ | |
return (this.tree !== null) && this.tree.addClass(id, className); | |
}; | |
/** | |
* Given a node's id, removes the given CSS class from this node. | |
* | |
* @param {string} id - The id of a node in an Autodesk.Viewing.Model | |
* @param {string} className - The CSS class to remove | |
* @returns {boolean} - true if the class was removed, false otherwise | |
*/ | |
ModelStructurePanel.prototype.removeClass = function(id, className) | |
{ | |
return (this.tree !== null) && this.tree.removeClass(id, className); | |
}; | |
Autodesk.Viewing.UI.ModelStructurePanel = ModelStructurePanel | |
var kDefaultDocStructureConfig = { | |
"click": { | |
"onObject": ["toggleLeavesSelection"] | |
}, | |
"clickShift": { | |
"onObject": ["toggleMultipleLeavesSelection"] | |
}, | |
"clickCtrl": { | |
"onObject": ["toggleMultipleLeavesSelection"] | |
} | |
}; | |
function MultiModelStructurePanel(viewer, title, options) { | |
this.viewer = viewer; | |
this.visible = false; | |
options = options || {}; | |
options.excludeRoot = options.excludeRoot !== undefined ? options.excludeRoot : true; | |
options.startCollapsed = options.startCollapsed !== undefined ? options.startCollapsed : true; | |
options.heightAdjustment = 75; //bigger than default because of search bar | |
ModelStructurePanel.call(this, viewer.container, viewer.container.id + 'MultiModelStructurePanel', title, options); | |
this.clickConfig = (options && options.docStructureConfig) ? options.docStructureConfig : kDefaultDocStructureConfig; | |
this.isMac = (navigator.userAgent.search("Mac OS") !== -1); | |
this.initSearchBox(); | |
this.prevSearchResults = []; | |
this.prevSearchString = ""; | |
// When selection changes in the viewer, the tree reflects the selection. | |
this.ignoreNextSelectionChange = false; | |
this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, function(event) { | |
if(!this.uiCreated) { | |
this.needsToScroll = true | |
return; | |
} | |
if (this.ignoreNextSelectionChange) { | |
this.ignoreNextSelectionChange = false; | |
return; | |
} | |
var selectedNodes = []; | |
var dbIds = event.dbIdArray; | |
for (var i = 0; i < dbIds.length; ++i) { | |
toggleLeafNodes(dbIds[i], this.tree, this.myDelegate, selectedNodes, selectedNodes); | |
} | |
this.setSelection(selectedNodes); | |
if (dbIds.length !== 0) { | |
if (this.visible) { | |
this.tree.scrollTo(dbIds[0]); | |
} else { | |
this.needsToScroll = true | |
} | |
} | |
}.bind(this)); | |
}; | |
MultiModelStructurePanel.prototype = Object.create(ModelStructurePanel.prototype); | |
MultiModelStructurePanel.prototype.constructor = MultiModelStructurePanel; | |
Autodesk.Viewing.Extensions.ViewerPanelMixin.call(MultiModelStructurePanel.prototype); | |
MultiModelStructurePanel.prototype.initialize = function () { | |
ModelStructurePanel.prototype.initialize.call(this); | |
var that = this; | |
that.addEventListener(that.viewer, Autodesk.Viewing.SELECTION_CHANGED_EVENT, function (event) { | |
that.setSelection(event.nodeArray.slice()); | |
}); | |
that.addEventListener(that.viewer, Autodesk.Viewing.ISOLATE_EVENT, function (event) { | |
that.setIsolation(event.nodeIdArray.slice()); | |
}); | |
that.addEventListener(that.viewer, Autodesk.Viewing.HIDE_EVENT, function (event) { | |
that.setHidden(event.nodeIdArray.slice(), true); | |
}); | |
that.addEventListener(that.viewer, Autodesk.Viewing.SHOW_EVENT, function (event) { | |
that.setHidden(event.nodeIdArray.slice(), false); | |
}); | |
}; | |
MultiModelStructurePanel.prototype.uninitialize = function () { | |
this.viewer = null; | |
ModelStructurePanel.prototype.uninitialize.call(this); | |
}; | |
MultiModelStructurePanel.prototype.createUI = function () { | |
ModelStructurePanel.prototype.createUI.call(this); | |
var treeNodesContainer = this.container.querySelector('#MultiModelStructurePanel-scroll-container'); | |
treeNodesContainer.addEventListener('contextmenu', function(event) { | |
this.viewer.contextMenu.show(event); | |
}.bind(this)); | |
}; | |
MultiModelStructurePanel.prototype.show = function(show) { | |
if (this.visible === show) { | |
return; | |
} | |
this.visible = show; | |
if (this.visible) { | |
this.setSelection(this.selectedNodes); | |
} | |
if (this.needsToScroll && this.visible && this.selectedNodes.length !== 0) { | |
this.tree.scrollTo(this.selectedNodes[0]); | |
this.needsToScroll = false; | |
} | |
} | |
MultiModelStructurePanel.prototype.handleAction = function (actionArray, dbId) { | |
for (var action in actionArray) { | |
switch (actionArray[action]) { | |
case "toggleLeavesSelection": | |
toggleLeavesSelection(this, dbId); | |
break; | |
case "toggleMultipleLeavesSelection": | |
toggleMultipleLeavesSelection(this, dbId); | |
break; | |
case "selectOnly": | |
this.ignoreNextSelectionChange = true; | |
this.viewer.select(dbId); | |
break; | |
case "deselectAll": | |
this.ignoreNextSelectionChange = true; | |
this.viewer.select([]); | |
break; | |
case "selectToggle": | |
this.ignoreNextSelectionChange = true; | |
this.viewer.toggleSelect(dbId); | |
break; | |
case "isolate": | |
this.viewer.isolate(dbId); | |
break; | |
case "showAll": | |
this.viewer.isolate(null); | |
break; | |
case "focus": | |
this.viewer.fitToView(); | |
break; | |
case "hide": | |
this.viewer.hide(dbId); | |
break; | |
case "show": | |
this.viewer.show(dbId); | |
break; | |
case "toggleVisibility": | |
this.viewer.toggleVisibility(dbId); | |
break; | |
} | |
} | |
}; | |
MultiModelStructurePanel.prototype.ctrlDown = function (event) { | |
return (this.isMac && event.metaKey) || (!this.isMac && event.ctrlKey); | |
}; | |
MultiModelStructurePanel.prototype.onClick = function (node, event) { | |
if (this.isMac && event.ctrlKey) { | |
return; | |
} | |
var that = this; | |
var key = "click"; | |
if (that.ctrlDown(event)) { | |
key += "Ctrl"; | |
} | |
if (event.shiftKey) { | |
key += "Shift"; | |
} | |
if (event.altKey) { | |
key += "Alt"; | |
} | |
if (this.clickConfig && this.clickConfig[key]) { | |
that.handleAction(this.clickConfig[key]["onObject"], node); | |
} else { | |
this.viewer.select(node); | |
} | |
}; | |
MultiModelStructurePanel.prototype.onDoubleClick = function (node, event) { | |
}; | |
MultiModelStructurePanel.prototype.onHover = function (node, event) { | |
}; | |
MultiModelStructurePanel.prototype.onRightClick = function (node, event) { | |
// Sometimes CTRL + LMB maps to a right click on a mac. Redirect it. | |
if (this.isMac && event.ctrlKey && event.button === 0) { | |
if (this.clickConfig && this.clickConfig["clickCtrl"]) { | |
this.handleAction(this.clickConfig["clickCtrl"]["onObject"], node); | |
} | |
return null; | |
} | |
return this.viewer.contextMenu.show(event); | |
}; | |
MultiModelStructurePanel.prototype.setHidden = function (nodes, hidden) { | |
if (!this.uiCreated) | |
this.createUI(); | |
var tree = this.tree; | |
if(!tree) { | |
return; | |
} | |
var action = hidden ? | |
function(node) { | |
tree.addClass(node, 'dim', false); | |
tree.removeClass(node, 'visible', false); | |
return true; | |
} : | |
function(node) { | |
tree.removeClass(node, 'dim', false); | |
tree.addClass(node, 'visible', false); | |
return true; | |
}; | |
for (var i = 0; i < nodes.length; ++i) { | |
tree.iterate(nodes[i], action); | |
} | |
}; | |
MultiModelStructurePanel.prototype.setIsolation = function (nodes) { | |
// roodId === 0 is a valid root node | |
if (this.rootId === undefined || this.rootId === null) { | |
return; | |
} | |
var tree = this.tree; | |
tree && tree.iterate(this.rootId, function (node) { | |
tree.removeClass(node, 'dim', false); | |
tree.removeClass(node, 'visible', false); | |
return true; | |
}); | |
if (nodes.length > 0) { | |
// If the root is isolated, we don't want to dim anything. | |
// | |
if (nodes.length === 1 && nodes[0] === this.rootId) { | |
return; | |
} | |
this.setHidden([this.rootId], true); | |
this.setHidden(nodes, false); | |
} | |
}; | |
MultiModelStructurePanel.prototype.initSearchBox = function () { | |
var searchbox = document.createElement("input"); | |
searchbox.className = "toolbar-search-box"; | |
searchbox.type = "search"; | |
searchbox.results = 5; | |
//searchbox.placeholder = Autodesk.Viewing.i18n.translate("Search"); | |
searchbox.placeholder = Autodesk.Viewing.i18n.translate("Filter by name"); | |
searchbox.incremental = "incremental"; | |
searchbox.autosave = this.container.id + "search_autosave"; | |
//searchbox.setAttribute("data-i18n", "[placeholder]Search"); | |
searchbox.setAttribute("data-i18n", "[placeholder]Filter by name"); | |
this.scrollContainer.parentNode.insertBefore(searchbox, this.scrollContainer); | |
this.searchbox = searchbox; | |
var viewer = this.viewer; | |
var self = this; | |
//ADP | |
var trackAdpFirstSearch = true; | |
function doSearch() { | |
function collapsePreviousSearch() { | |
for (var i=0; i<self.prevSearchResults.length; i++) { | |
//self.tree.removeClass(self.prevSearchResults[i], "searchHit"); | |
self.tree.setCollapsed(self.prevSearchResults[i], true, true); | |
} | |
if (self.options.excludeRoot) { | |
self.tree.setCollapsed(self.rootId, false, false); | |
} | |
} | |
if (self.isSearching) { | |
return; //don't send another search to the worker if one is in progress | |
} | |
if (searchbox.value.length === 0) { | |
self.isSearching = false; | |
self.prevSearchString = ""; | |
collapsePreviousSearch(); | |
self.tree.setSelection([]); | |
viewer.select([]); | |
} else { | |
if (self.prevSearchString === searchbox.value) { | |
return; | |
} | |
self.prevSearchString = searchbox.value; | |
collapsePreviousSearch(); | |
if(trackAdpFirstSearch) { | |
Autodesk.Viewing.Private.logger.track({category:'search_node', name: 'model_browser_tool'}) | |
trackAdpFirstSearch = false; | |
} | |
self.isSearching = true; | |
viewer.search(searchbox.value, function (resultIds) { | |
var selectedNodes = []; | |
// var selectedNodes = resultIds; | |
for (var i = 0; i < resultIds.length; ++i) { | |
self.tree.setCollapsed(resultIds[i], false, true); | |
gatherLeaves(resultIds[i], self.tree, self.myDelegate, selectedNodes); | |
// toggleLeafNodes(resultIds[i], self.tree, self.myDelegate, selectedNodes, selectedNodes); | |
} | |
self.ignoreNextSelectionChange = true; | |
self.viewer.select(selectedNodes); | |
self.setSelection(selectedNodes); | |
if (selectedNodes.length !== 0) { | |
self.viewer.fitToView(selectedNodes); | |
} | |
if (resultIds.length) { | |
self.resizeToContent(); | |
self.tree.scrollTo(resultIds[0]); | |
} | |
self.prevSearchResults = resultIds; | |
self.isSearching = false; | |
}, null, ["name"]); | |
} | |
} | |
var TIMEOUT = 500; | |
var timeout; | |
searchbox.addEventListener("input", function(e) { // delayed: as typing | |
clearTimeout(timeout); | |
timeout = setTimeout(doSearch, TIMEOUT); | |
}); | |
searchbox.addEventListener("change", function(e) { // immediate: press enter, lose focus | |
clearTimeout(timeout); | |
doSearch(); | |
if (e.target === document.activeElement) { // focus lost. | |
self.prevSearchString = ""; | |
} | |
}); | |
}; | |
function gatherLeaves(dbId, tree, delegate, leaves) { | |
tree.iterate(dbId, function(node) { | |
if(!delegate.isTreeNodeGroup(node) && leaves.indexOf(node) === -1) { | |
leaves.push(node); | |
} | |
return true; | |
}); | |
} | |
function toggleLeafNodes(dbId, tree, delegate, selectedLeaves, leaves) { | |
var unselectedLeaves = false; | |
var leavesFound = []; | |
tree.iterate(dbId, function(node) { | |
if(!delegate.isTreeNodeGroup(node)) { | |
unselectedLeaves |= selectedLeaves.indexOf(node) === -1; | |
leavesFound.push(node); | |
} | |
return true; | |
}); | |
var leavesFoundCount = leavesFound.length; | |
for (var i = 0; i < leavesFoundCount; ++i) { | |
var node = leavesFound[i]; | |
var index = leaves.indexOf(node); | |
if (unselectedLeaves) { | |
if (index === -1) { | |
leaves.push(node) | |
} | |
} else { | |
if (index !== -1) { | |
leaves.splice(index, 1); | |
} | |
} | |
} | |
return unselectedLeaves; | |
} | |
function toggleLeavesSelection(self, dbId) { | |
var selectedNodes = []; | |
if(!toggleLeafNodes(dbId, self.tree, self.myDelegate, self.selectedNodes, selectedNodes)) { | |
selectedNodes = []; | |
} | |
self.ignoreNextSelectionChange = true; | |
self.viewer.select(selectedNodes); | |
self.setSelection(selectedNodes); | |
if (selectedNodes.length !== 0) { | |
self.viewer.fitToView(selectedNodes); | |
} | |
} | |
function toggleMultipleLeavesSelection(self, dbId) { | |
var selectedNodes = self.selectedNodes.slice(0); | |
toggleLeafNodes(dbId, self.tree, self.myDelegate, self.selectedNodes, selectedNodes); | |
self.ignoreNextSelectionChange = true; | |
self.viewer.select(selectedNodes); | |
self.setSelection(selectedNodes); | |
if (selectedNodes.length !== 0) { | |
self.viewer.fitToView(selectedNodes); | |
} | |
} | |
Autodesk.Viewing.Extensions.ViewerModelStructurePanel = MultiModelStructurePanel |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment