Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*******************************************************************************
* Copyright (c) 2014-2015 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
scout.Tree = function() {
scout.Tree.parent.call(this);
this.$data;
this.nodes = []; // top-level nodes
this.visibleNodesFlat = [];
this.visibleNodesMap = {};
this.nodesMap = {}; // all nodes by id
this._addAdapterProperties(['menus', 'keyStrokes']);
this._additionalContainerClasses = ''; // may be used by subclasses to set additional CSS classes
this._treeItemPaddingLeft = 23;
this._treeItemCheckBoxPaddingLeft = 29;
this._treeItemPaddingLevel = 15;
this.menus = [];
this.menuBar;
this.checkedNodes = [];
this._filters = [];
this._doubleClickSupport = new scout.DoubleClickSupport();
this._$animationWrapper; // used by _renderExpansion()
this._$expandAnimationWrappers = [];
this._filterMenusHandler = this._filterMenus.bind(this);
//contains all parents of a selected node, the selected node and the first level children
this._inSelectionPathList = {};
this.groupedNodes = {};
this.viewRangeRendered = new scout.Range(0, 0);
this.viewRangeSize = 20;
this.startAnimationFunc = function() {
this.runningAnimations++;
}.bind(this);
this.runningAnimations = 0;
this.runningAnimationsFinishFunc = function() {
this.runningAnimations--;
if (this.runningAnimations <= 0) {
this.runningAnimations = 0;
this._renderViewportBlocked = false;
this.invalidateLayoutTree();
}
}.bind(this);
this.nodeHeight = 0;
this.nodeWidth = 0;
this.maxNodeWidth = 0;
this.nodeWidthDirty = false;
this._scrolldirections = 'both';
};
scout.inherits(scout.Tree, scout.ModelAdapter);
scout.Tree.DisplayStyle = {
DEFAULT: 'default',
BREADCRUMB: 'breadcrumb'
};
scout.Tree.prototype._init = function(model) {
scout.Tree.parent.prototype._init.call(this, model);
this.addFilter(new scout.LazyNodeFilter(this), true);
this.breadcrumbFilter = new scout.TreeBreadcrumbFilter(this);
if (this.displayStyle === scout.Tree.DisplayStyle.BREADCRUMB) {
this.addFilter(this.breadcrumbFilter, true, true);
}
this.initialTraversing = true;
this._visitNodes(this.nodes, this._initTreeNode.bind(this));
this._visitNodes(this.nodes, this._updateFlatListAndSelectionPath.bind(this));
this.initialTraversing = false;
this.selectedNodes = this._nodesByIds(this.selectedNodes);
this.menuBar = scout.create('MenuBar', {
parent: this,
menuOrder: new scout.MenuItemsOrder(this.session, 'Tree'),
menuFilter: this._filterMenusHandler
});
this.menuBar.bottom();
this._updateItemPath(true);
this._syncDisplayStyle(this.displayStyle);
this._syncKeyStrokes(this.keyStrokes);
this._syncMenus(this.menus);
};
/**
* @override ModelAdapter.js
*/
scout.Tree.prototype._initKeyStrokeContext = function(keyStrokeContext) {
scout.Tree.parent.prototype._initKeyStrokeContext.call(this, keyStrokeContext);
this._initTreeKeyStrokeContext(keyStrokeContext);
};
scout.Tree.prototype._initTreeKeyStrokeContext = function(keyStrokeContext) {
var modifierBitMask = scout.keyStrokeModifier.NONE;
keyStrokeContext.registerKeyStroke([
new scout.TreeSpaceKeyStroke(this),
new scout.TreeNavigationUpKeyStroke(this, modifierBitMask),
new scout.TreeNavigationDownKeyStroke(this, modifierBitMask),
new scout.TreeCollapseAllKeyStroke(this, modifierBitMask),
new scout.TreeCollapseOrDrillUpKeyStroke(this, modifierBitMask),
new scout.TreeNavigationEndKeyStroke(this, modifierBitMask),
new scout.TreeExpandOrDrillDownKeyStroke(this, modifierBitMask)
]);
// Prevent default action and do not propagate ↓ or ↑ keys if ctrl- or alt-modifier is not pressed.
// Otherwise, an '↑-event' on the first node, or an '↓-event' on the last row will bubble up (because not consumed by tree navigation keystrokes) and cause a superior tree to move its selection;
// Use case: - outline tree with a detail form that contains a tree;
// - preventDefault because of smartfield, so that the cursor is not moved on first or last row;
keyStrokeContext.registerStopPropagationInterceptor(function(event) {
if (!event.ctrlKey && !event.altKey && scout.isOneOf(event.which, scout.keys.UP, scout.keys.DOWN)) {
event.stopPropagation();
event.preventDefault();
}
});
};
scout.Tree.prototype._syncMenus = function(menus, oldMenus) {
this.updateKeyStrokes(menus, oldMenus);
this.menus = menus;
this._updateMenuBar();
};
scout.Tree.prototype._updateMenuBar = function() {
var menuItems = this._filterMenus(this.menus, scout.MenuDestinations.MENU_BAR, false, true);
this.menuBar.setMenuItems(menuItems);
};
scout.Tree.prototype._syncKeyStrokes = function(keyStrokes, oldKeyStrokes) {
this.updateKeyStrokes(keyStrokes, oldKeyStrokes);
this.keyStrokes = keyStrokes;
};
scout.Tree.prototype._syncDisplayStyle = function(newValue) {
this.setDisplayStyle(newValue, false);
};
scout.Tree.prototype._resetTreeNode = function(node, parentNode) {
node.rendered = false;
node.attached = false;
delete node.$node;
};
scout.Tree.prototype._isSelectedNode = function(node) {
if (this.initialTraversing) {
return this.selectedNodes.indexOf(node.id) > -1;
} else {
return this.selectedNodes.indexOf(node) > -1;
}
};
scout.Tree.prototype._updateFlatListAndSelectionPath = function(node, parentNode) {
// if this node is selected all parent nodes have to be added to selectionPath
if (this._isSelectedNode(node) && ((node.parentNode && !this.visibleNodesMap[node.parentNode.id]) || node.level === 0)) {
var p = node;
while (p) {
this._inSelectionPathList[p.id] = true;
p.filterDirty = true;
if (p !== node) {
// ensure node is expanded
node.expanded = true;
// if parent was filtered before, try refilter after adding to selection path.
if (p.level === 0) {
this._applyFiltersForNode(p);
// add visible nodes to visible nodes array when they are initialized
this._addToVisibleFlatList(p, false);
// process children
this._addChildrenToFlatList(p, this.visibleNodesFlat.length - 1, false, null, true);
}
}
p = p.parentNode;
}
} else if (node.parentNode && this._isSelectedNode(node.parentNode)) {
this._inSelectionPathList[node.id] = true;
}
this._applyFiltersForNode(node);
// add visible nodes to visible nodes array when they are initialized
this._addToVisibleFlatList(node, false);
};
scout.Tree.prototype._initTreeNode = function(node, parentNode) {
this.nodesMap[node.id] = node;
if (parentNode) {
node.parentNode = parentNode;
node.level = node.parentNode.level + 1;
} else {
node.level = 0;
}
node.rendered = false;
node.attached = false;
// create function to check if node is in hierarchy of a parent. is used on removal from flat list.
node.isChildOf = function(parentNode) {
if (parentNode === this.parentNode) {
return true;
} else if (!this.parentNode) {
return false;
}
return this.parentNode.isChildOf(parentNode);
};
if (node.checked) {
this.checkedNodes.push(node);
}
scout.defaultValues.applyTo(node, 'TreeNode');
if (node.childNodes === undefined) {
node.childNodes = [];
}
var that = this;
this._initTreeNodeInternal(node, parentNode);
node.isFilterAccepted = function(forceFilter) {
if (this.filterDirty || forceFilter) {
that._applyFiltersForNode(this);
}
return this.filterAccepted;
};
this._updateMarkChildrenChecked(node, true, node.checked);
node.initialized = true;
};
scout.Tree.prototype._initTreeNodeInternal = function(node, parentNode) {
// override this if you want a custom node init before filtering.
};
scout.Tree.prototype.destroy = function() {
scout.Tree.parent.prototype.destroy.call(this);
this._visitNodes(this.nodes, this._destroyTreeNode.bind(this));
};
scout.Tree.prototype._destroyTreeNode = function(node, parentNode) {
delete this.nodesMap[node.id];
scout.arrays.remove(this.selectedNodes, node); // ensure deleted node is not in selection list anymore (in case the model does not update the selection)
this._removeFromFlatList(node, false); //ensure node is not longer in visible nodes list.
if (this._onNodeDeleted) { // Necessary for subclasses
this._onNodeDeleted(node);
}
};
/**
* if func returns true the children of the visited node are not visited.
*/
scout.Tree.prototype._visitNodes = function(nodes, func, parentNode) {
var i, node;
if (!nodes) {
return;
}
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
var doNotProcessChildren = func(node, parentNode);
if (!doNotProcessChildren && node.childNodes.length > 0) {
this._visitNodes(node.childNodes, func, node);
}
}
};
scout.Tree.prototype._render = function($parent) {
this.$container = $parent.appendDiv('tree');
if (this._additionalContainerClasses) {
this.$container.addClass(this._additionalContainerClasses);
}
var layout = new scout.TreeLayout(this);
this.htmlComp = new scout.HtmlComponent(this.$container, this.session);
this.htmlComp.setLayout(layout);
this.htmlComp.pixelBasedSizing = false;
this.$data = this.$container.appendDiv('tree-data')
.on('contextmenu', this._onContextMenu.bind(this))
.on('mousedown', '.tree-node', this._onNodeMouseDown.bind(this))
.on('mouseup', '.tree-node', this._onNodeMouseUp.bind(this))
.on('dblclick', '.tree-node', this._onNodeDoubleClick.bind(this))
.on('mousedown', '.tree-node-control', this._onNodeControlMouseDown.bind(this))
.on('mouseup', '.tree-node-control', this._onNodeControlMouseUp.bind(this))
.on('dblclick', '.tree-node-control', this._onNodeControlDoubleClick.bind(this))
.on('scroll', this._onDataScroll.bind(this));
new scout.HtmlComponent(this.$data, this.session);
if (this.isHorizontalScrollingEnabled()) {
this.$data.toggleClass('scrollable-tree', true);
}
scout.scrollbars.install(this.$data, {
parent: this,
axis: this._scrolldirections
});
this._installNodeTooltipSupport();
this.menuBar.render(this.$container);
this._updateNodeDimensions();
// render display style before viewport (not in renderProperties) to have a correct style from the beginning
this._renderDisplayStyle();
this._renderViewport();
};
scout.Tree.prototype._postRender = function() {
this._renderSelection();
};
scout.Tree.prototype._remove = function() {
// stop all animations
if (this._$animationWrapper) {
this._$animationWrapper.stop(false, true);
}
// Detach nodes from jQuery objects (because those will be removed)
this._visitNodes(this.nodes, this._resetTreeNode.bind(this));
scout.scrollbars.uninstall(this.$data, this.session);
this._uninstallDragAndDropHandler();
this._uninstallNodeTooltipSupport();
this.$fillBefore = null;
this.$fillAfter = null;
this.$data = null;
// reset rendered view range because now range is rendered
this.viewRangeRendered = new scout.Range(0, 0);
scout.Tree.parent.prototype._remove.call(this);
};
scout.Tree.prototype._renderProperties = function() {
scout.Tree.parent.prototype._renderProperties.call(this);
this._renderEnabled();
this._renderMenus();
this._renderDropType();
};
scout.Tree.prototype.isHorizontalScrollingEnabled = function() {
return this._scrolldirections === 'both' || this._scrolldirections === 'x';
};
scout.Tree.prototype._onDataScroll = function() {
var scrollToSelectionBackup = this.scrollToSelection;
this.scrollToSelection = false;
var scrollTop = this.$data[0].scrollTop;
if (this.scrollTop === scrollTop) {
return;
}
this._renderViewport();
this.scrollTop = scrollTop;
this.scrollToSelection = scrollToSelectionBackup;
};
scout.Table.prototype.setScrollTop = function(scrollTop) {
scout.scrollbars.scrollTop(this.$data, scrollTop);
this.scrollTop = scrollTop;
// call _renderViewport to make sure nodes are rendered immediately. The browser fires the scroll event handled by onDataScroll delayed
this._renderViewport();
};
scout.Tree.prototype._renderViewport = function() {
if (this.runningAnimations > 0 || this._renderViewportBlocked) {
//animation pending do not render view port because finishing should rerenderViewport
return;
}
var viewRange = this._calculateCurrentViewRange();
this._renderViewRange(viewRange);
};
scout.Tree.prototype._calculateCurrentViewRange = function() {
var node,
scrollTop = this.$data[0].scrollTop,
maxScrollTop = this.$data[0].scrollHeight - this.$data[0].clientHeight;
if (maxScrollTop === 0 && this.visibleNodesFlat.length > 0) {
// no scrollbars visible
node = this.visibleNodesFlat[0];
} else {
node = this._nodeAtScrollTop(scrollTop);
}
return this._calculateViewRangeForNode(node);
};
scout.Tree.prototype._rerenderViewport = function() {
if (this._renderViewportBlocked) {
return;
}
this._removeRenderedNodes();
this._renderFiller();
this._updateDomNodeWidth();
this._renderViewport();
};
scout.Tree.prototype._removeRenderedNodes = function() {
var $nodes = this.$data.find('.tree-node');
$nodes.each(function(i, elem) {
var $node = $(elem),
node = $node.data('node');
if ($node.hasClass('hiding')) {
// Do not remove nodes which are removed using an animation
return;
}
this._removeNode(node);
}.bind(this));
this.viewRangeRendered = new scout.Range(0, 0);
};
scout.Tree.prototype._renderViewRangeForNode = function(node) {
var viewRange = this._calculateViewRangeForNode(node);
this._renderViewRange(viewRange);
};
scout.Tree.prototype._renderNodesInRange = function(range) {
var prepend = false;
var nodes = this.visibleNodesFlat;
if (nodes.length === 0) {
return;
}
var maxRange = new scout.Range(0, nodes.length);
range = maxRange.intersect(range);
if (!range.intersect(this.viewRangeRendered).equals(new scout.Range(0, 0))) {
throw new Error('New range must not intersect with existing.');
}
if (range.to <= this.viewRangeRendered.from) {
prepend = true;
}
var newRange = this.viewRangeRendered.union(range);
if (newRange.length === 2) {
throw new Error('Can only prepend or append rows to the existing range. Existing: ' + this.viewRangeRendered + '. New: ' + newRange);
}
this.viewRangeRendered = newRange[0];
var numNodesRendered = this.ensureRangeVisible(range);
if ($.log.isTraceEnabled()) {
$.log.trace(numNodesRendered + ' new nodes rendered from ' + range);
}
};
scout.Tree.prototype.ensureRangeVisible = function(range) {
var nodes = this.visibleNodesFlat,
numNodesRendered = 0;
for (var r = range.from; r < range.to; r++) {
var node = nodes[r];
if (!node.attached) {
this._insertNodeInDOM(node);
numNodesRendered++;
}
}
return numNodesRendered;
};
scout.Tree.prototype._renderFiller = function() {
if (!this.$fillBefore) {
this.$fillBefore = this.$data.prependDiv('tree-data-fill');
}
var fillBeforeDimensions = this._calculateFillerDimension(new scout.Range(0, this.viewRangeRendered.from));
this.$fillBefore.cssHeight(fillBeforeDimensions.height);
if (this.isHorizontalScrollingEnabled()) {
this.$fillBefore.cssWidth(fillBeforeDimensions.width);
this.maxNodeWidth = Math.max(fillBeforeDimensions.width, this.maxNodeWidth);
}
$.log.trace('FillBefore height: ' + fillBeforeDimensions.height);
if (!this.$fillAfter) {
this.$fillAfter = this.$data.appendDiv('tree-data-fill');
}
var fillAfterDimensions = {
height: 0,
width: 0
};
fillAfterDimensions = this._calculateFillerDimension(new scout.Range(this.viewRangeRendered.to, this.visibleNodesFlat.length));
this.$fillAfter.cssHeight(fillAfterDimensions.height);
if (this.isHorizontalScrollingEnabled()) {
this.$fillAfter.cssWidth(fillAfterDimensions.width);
this.maxNodeWidth = Math.max(fillAfterDimensions.width, this.maxNodeWidth);
}
$.log.trace('FillAfter height: ' + fillAfterDimensions.height);
};
scout.Tree.prototype._calculateFillerDimension = function(range) {
var dimension = {
height: 0,
width: Math.max(this.$data.outerWidth(), this.maxNodeWidth)
};
for (var i = range.from; i < range.to; i++) {
var node = this.visibleNodesFlat[i];
dimension.height += this._heightForNode(node);
dimension.width = Math.max(dimension.width, this._widthForNode(node));
}
return dimension;
};
scout.Tree.prototype._removeNodesInRange = function(range) {
var fromNode, toNode, node, i,
numNodesRemoved = 0,
nodes = this.visibleNodesFlat;
var maxRange = new scout.Range(0, nodes.length);
range = maxRange.intersect(range);
fromNode = nodes[range.from];
toNode = nodes[range.to];
var newRange = this.viewRangeRendered.subtract(range);
if (newRange.length === 2) {
throw new Error('Can only remove nodes at the beginning or end of the existing range. ' + this.viewRangeRendered + '. New: ' + newRange);
}
this.viewRangeRendered = newRange[0];
for (i = range.from; i < range.to; i++) {
node = nodes[i];
this._removeNode(node);
numNodesRemoved++;
}
if ($.log.isTraceEnabled()) {
$.log.trace(numNodesRemoved + ' nodes removed from ' + range + '.');
}
};
/**
* Just removes the node, does NOT adjust this.viewRangeRendered
*/
scout.Tree.prototype._removeNode = function(node) {
var $node = node.$node;
if (!$node) {
return;
}
if ($node.hasClass('hiding')) {
// Do not remove nodes which are removed using an animation
return;
}
//only remove node
$node.detach();
node.attached = false;
};
/**
* Renders the rows visible in the viewport and removes the other rows
*/
scout.Tree.prototype._renderViewRange = function(viewRange) {
if (viewRange.from === this.viewRangeRendered.from && viewRange.to === this.viewRangeRendered.to && !this.viewRangeDirty) {
// Range already rendered -> do nothing
return;
}
if (!this.viewRangeDirty) {
var rangesToRender = viewRange.subtract(this.viewRangeRendered);
var rangesToRemove = this.viewRangeRendered.subtract(viewRange);
var maxRange = new scout.Range(0, this.visibleNodesFlat.length);
rangesToRemove.forEach(function(range) {
this._removeNodesInRange(range);
if (maxRange.to < range.to) {
this.viewRangeRendered = viewRange;
}
}.bind(this));
rangesToRender.forEach(function(range) {
this._renderNodesInRange(range);
}.bind(this));
} else {
//expansion changed
this.viewRangeRendered = viewRange;
this.ensureRangeVisible(viewRange);
}
// check if at least last and first row in range got correctly rendered
if (this.viewRangeRendered.size() > 0) {
var nodes = this.visibleNodesFlat;
var firstNode = nodes[this.viewRangeRendered.from];
var lastNode = nodes[this.viewRangeRendered.to - 1];
if (this.viewRangeDirty) {
// cleanup nodes before range and after
var $nodesBeforFirstNode = firstNode.$node.prevAll('.tree-node');
var $nodesAfterLastNode = lastNode.$node.nextAll('.tree-node');
this._cleanupNodes($nodesBeforFirstNode);
this._cleanupNodes($nodesAfterLastNode);
}
if (!firstNode.attached || !lastNode.attached) {
throw new Error('Nodes not rendered as expected. ' + this.viewRangeRendered + '. First: ' + firstNode.$node + '. Last: ' + lastNode.$node);
}
}
this._postRenderViewRange();
this.viewRangeDirty = false;
};
scout.Tree.prototype._postRenderViewRange = function() {
this._renderFiller();
this._updateDomNodeWidth();
this._renderSelection();
};
scout.Tree.prototype._updateDomNodeWidth = function($nodes) {
if (!this.isHorizontalScrollingEnabled()) {
return;
}
if (this.rendered && this.nodeWidthDirty) {
for (var i = this.viewRangeRendered.from; i < this.viewRangeRendered.to; i++) {
this.maxNodeWidth = Math.max(this.visibleNodesFlat[i].width, this.maxNodeWidth);
}
var width = Math.max(this.maxNodeWidth, this.$data.outerWidth());
this.$data.find('.tree-node').css('width', this.maxNodeWidth);
this.nodeWidthDirty = false;
}
};
scout.Tree.prototype._cleanupNodes = function($nodes) {
for (var i = 0; i < $nodes.length; i++) {
this._removeNode($nodes.eq(i).data('node'));
}
};
/**
* Returns the index of the node which is at position scrollTop.
*/
scout.Tree.prototype._nodeAtScrollTop = function(scrollTop) {
var height = 0,
nodeTop;
this.visibleNodesFlat.some(function(node, i) {
height += this._heightForNode(node);
if (scrollTop < height) {
nodeTop = node;
return true;
}
}.bind(this));
var visibleNodesLength = this.visibleNodesFlat.length;
if (!nodeTop && visibleNodesLength > 0) {
nodeTop = this.visibleNodesFlat[visibleNodesLength - 1];
}
return nodeTop;
};
scout.Tree.prototype._heightForNode = function(node) {
var height = 0;
if (node.height) {
height = node.height;
} else {
height = this.nodeHeight;
}
return height;
};
scout.Tree.prototype._widthForNode = function(node) {
var width = 0;
if (node.width) {
width = node.width;
} else {
width = this.nodeWidth;
}
return width;
};
/**
* Returns a range of size this.viewRangeSize. Start of range is nodeIndex - viewRangeSize / 4.
* -> 1/4 of the nodes are before the viewport 2/4 in the viewport 1/4 after the viewport,
* assuming viewRangeSize is 2*number of possible nodes in the viewport (see calculateViewRangeSize).
*/
scout.Tree.prototype._calculateViewRangeForNode = function(node) {
var viewRange = new scout.Range(),
quarterRange = Math.floor(this.viewRangeSize / 4),
diff;
var nodeIndex = this.visibleNodesFlat.indexOf(node);
viewRange.from = Math.max(nodeIndex - quarterRange, 0);
viewRange.to = Math.min(viewRange.from + this.viewRangeSize, this.visibleNodesFlat.length);
if (!node || nodeIndex === -1) {
return viewRange;
}
// Try to use the whole viewRangeSize (extend from if necessary)
diff = this.viewRangeSize - viewRange.size();
if (diff > 0) {
viewRange.from = Math.max(viewRange.to - this.viewRangeSize, 0);
}
return viewRange;
};
/**
* Calculates the optimal view range size (number of nodes to be rendered).
* It uses the default node height to estimate how many nodes fit in the view port.
* The view range size is this value * 2.
*/
scout.Tree.prototype.calculateViewRangeSize = function() {
// Make sure row height is up to date (row height may be different after zooming)
this._updateNodeDimensions();
if (this.nodeHeight === 0) {
throw new Error('Cannot calculate view range with nodeHeight = 0');
}
return Math.ceil(this.$data.outerHeight() / this.nodeHeight) * 2;
};
scout.Tree.prototype.setViewRangeSize = function(viewRangeSize) {
if (this.viewRangeSize === viewRangeSize) {
return;
}
this.viewRangeSize = viewRangeSize;
if (this.rendered) {
this._renderViewport();
}
};
scout.Tree.prototype._updateNodeDimensions = function() {
var node = {
level: 0
};
var $emptyNode = this._$buildNode(node).appendTo(this.$data);
this._renderNodeText(node);
this.nodeHeight = $emptyNode.outerHeight(true);
if (this.isHorizontalScrollingEnabled()) {
var oldNodeWidth = this.nodeWidth;
this.nodeWidth = $emptyNode.outerWidth(true);
if (oldNodeWidth !== this.nodeWidth) {
this.viewRangeDirty = true;
}
}
$emptyNode.remove();
};
/**
* Updates the node heights for every visible node and clears the height of the others
*/
scout.Tree.prototype.updateNodeHeights = function() {
this.visibleNodesFlat.forEach(function(node) {
if (!node.attached) {
node.height = null;
} else {
node.height = node.$node.outerHeight(true);
}
});
};
/**
* @param parentNode optional. If provided, this node's state will be updated (e.g. it will be collapsed)
*/
scout.Tree.prototype._removeNodes = function(nodes, parentNode) {
if (nodes.length === 0) {
return;
}
nodes.forEach(function(node) {
this._removeFromFlatList(node, true);
if (node.childNodes.length > 0) {
this._removeNodes(node.childNodes, node);
}
if (node.$node) {
if (this._$animationWrapper && this._$animationWrapper.find(node.$node).length > 0) {
this._$animationWrapper.stop(false, true);
}
node.$node.remove();
node.rendered = false;
node.attached = false;
delete node.$node;
}
}, this);
//If every child node was deleted mark node as collapsed (independent of the model state)
//--> makes it consistent with addNodes and expand (expansion is not allowed if there are no child nodes)
var $parentNode = (parentNode ? parentNode.$node : undefined);
if ($parentNode) {
var childNodesOfParent = parentNode.childNodes;
if (!childNodesOfParent || childNodesOfParent.length === 0) {
$parentNode.removeClass('expanded');
$parentNode.removeClass('lazy');
}
}
if (this.rendered) {
this.viewRangeDirty = true;
this.invalidateLayoutTree();
}
};
scout.Tree.prototype._$buildNode = function(node) {
var $node = this.$container.makeDiv('tree-node')
.data('node', node)
.attr('data-nodeid', node.id)
.attr('data-level', node.level)
.css('padding-left', this._computeTreeItemPaddingLeft(node.level));
node.$node = $node;
$node.appendSpan('text');
this._renderTreeItemControl($node);
if (this.checkable) {
this._renderTreeItemCheckbox(node);
}
return $node;
};
scout.Tree.prototype._decorateNode = function(node) {
var formerClasses,
$node = node.$node;
if (!$node) {
// This node is not yet rendered, nothing to do
return;
}
formerClasses = 'tree-node';
if ($node.isSelected()) {
formerClasses += ' selected';
}
if ($node.hasClass('ancestor-of-selected')) {
formerClasses += ' ancestor-of-selected';
}
if ($node.hasClass('parent-of-selected')) {
formerClasses += ' parent-of-selected';
}
$node.removeClass();
$node.addClass(formerClasses);
$node.addClass(node.cssClass);
$node.toggleClass('leaf', !!node.leaf);
$node.toggleClass('expanded', (!!node.expanded && node.childNodes.length > 0));
$node.toggleClass('lazy', $node.hasClass('expanded') && node.expandedLazy);
$node.toggleClass('group', !!this.groupedNodes[node.id]);
$node.setEnabled(!!node.enabled);
$node.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('disabled', !(this.enabled && node.enabled));
if (!node.parentNode && this.selectedNodes.length === 0) {
// Root nodes have class child-of-selected if no node is selected
$node.addClass('child-of-selected');
} else if (node.parentNode && this.selectedNodes.indexOf(node.parentNode) > -1) {
$node.addClass('child-of-selected');
}
this._renderNodeText(node);
scout.styles.legacyStyle(node, $node);
// TODO [6.1] bsh: More attributes...
// iconId
// If parent node is marked as 'lazy', check if any visible child nodes remain.
if (node.parentNode && node.parentNode.expandedLazy) {
var hasVisibleNodes = node.parentNode.childNodes.some(function(childNode) {
if (this.visibleNodesMap[childNode.id]) {
return true;
}
}.bind(this));
if (!hasVisibleNodes && node.parentNode.$node) {
// Remove 'lazy' from parent
node.parentNode.$node.removeClass('lazy');
}
}
};
scout.Tree.prototype._renderTreeItemControl = function($node) {
var $control = $node.prependDiv('tree-node-control');
if (this.checkable) {
$control.addClass('checkable');
}
};
scout.Tree.prototype._renderTreeItemCheckbox = function(node) {
var $node = node.$node,
$controlItem = $node.prependDiv('tree-node-checkbox');
var $checkboxDiv = $controlItem
.appendDiv('check-box')
.toggleClass('checked', node.checked)
.toggleClass('disabled', !(this.enabled && node.enabled));
if (node.childrenChecked) {
$checkboxDiv.toggleClass('children-checked', true);
} else {
$checkboxDiv.toggleClass('children-checked', false);
}
};
scout.Tree.prototype._renderNodeText = function(node) {
var $node = node.$node,
$text = $node.children('.text');
if (node.htmlEnabled) {
$text.html(node.text);
} else {
$text.textOrNbsp(node.text);
}
};
scout.Tree.prototype._renderNodeChecked = function(node) {
if (!node.$node) {
// if node is not rendered, do nothing
return;
}
node.$node
.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('checked', node.checked);
};
scout.Tree.prototype._renderMenus = function() {
// NOP
};
scout.Tree.prototype._removeMenus = function() {
// menubar takes care about removal
};
scout.Tree.prototype._filterMenus = function(menus, destination, onlyVisible, enableDisableKeyStroke) {
return scout.menus.filterAccordingToSelection('Tree', this.selectedNodes.length, menus, destination, onlyVisible, enableDisableKeyStroke);
};
scout.Tree.prototype._renderEnabled = function() {
var enabled = this.enabled;
this.$data.setEnabled(enabled);
this.$container.setTabbable(enabled);
if (this.rendered) {
// Enable/disable all checkboxes
this.$nodes().each(function() {
var $node = $(this),
node = $node.data('node');
$node.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('disabled', !(enabled && node.enabled));
});
}
};
scout.Tree.prototype._renderTitle = function() {
// NOP
};
scout.Tree.prototype._renderAutoCheckChildren = function() {
// NOP
};
scout.Tree.prototype._renderCheckable = function() {
// Define helper functions
var isNodeRendered = function(node) {
return !!node.$node;
};
var updateCheckableStateRec = function(node) {
var $node = node.$node;
var $control = $node.children('.tree-node-control');
var $checkbox = $node.children('.tree-node-checkbox');
if (this.checkable) {
$control.addClass('checkable');
if ($checkbox.length === 0) {
this._renderTreeItemCheckbox(node);
}
} else {
$control.removeClass('checkable');
$checkbox.remove();
}
$node.css('padding-left', this._computeTreeItemPaddingLeft(parseFloat($node.attr('data-level'))));
// Recursion
if (node.childNodes) {
node.childNodes.filter(isNodeRendered).forEach(updateCheckableStateRec);
}
}.bind(this);
// Start recursion
this.nodes.filter(isNodeRendered).forEach(updateCheckableStateRec);
};
scout.Tree.prototype._renderMultiCheck = function() {
// NOP
};
scout.Tree.prototype._renderDisplayStyle = function() {
this.$container.toggleClass('breadcrumb', this.isBreadcrumbStyleActive());
// update scrollbar if mode has changed (from tree to bc or vice versa)
this.invalidateLayoutTree();
};
scout.Tree.prototype._renderExpansion = function(node, options) {
var opts = {
expandLazyChanged: false,
expansionChanged: false
};
$.extend(opts, options);
var $node = node.$node,
expanded = node.expanded;
// Only render if node is rendered to make it possible to expand/collapse currently hidden nodes (used by collapseAll).
if (!$node || $node.length === 0) {
return;
}
// Only expand / collapse if there are child nodes
if (node.childNodes.length === 0) {
return true;
}
$node.toggleClass('lazy', expanded && node.expandedLazy);
if (!opts.expansionChanged && !opts.expandLazyChanged) {
// Expansion state has not changed -> return
return;
}
if (expanded) {
$node.addClass('expanded');
} else {
$node.removeClass('expanded');
}
};
scout.Tree.prototype._renderSelection = function() {
// Add children class to root nodes if no nodes are selected
if (this.selectedNodes.length === 0) {
this.nodes.forEach(function(childNode) {
if (childNode.rendered) {
childNode.$node.addClass('child-of-selected');
}
}, this);
}
this.selectedNodes.forEach(function(node) {
if (!this.visibleNodesMap[node.id]) {
return;
}
// Mark all ancestor nodes, especially necessary for bread crumb mode
var parentNode = node.parentNode;
if (parentNode && parentNode.rendered) {
parentNode.$node.addClass('parent-of-selected');
}
while (parentNode) {
if (parentNode.rendered) {
parentNode.$node.addClass('ancestor-of-selected');
}
parentNode = parentNode.parentNode;
}
// Mark all child nodes
if (node.expanded) {
node.childNodes.forEach(function(childNode) {
if (childNode.rendered) {
childNode.$node.addClass('child-of-selected');
}
}, this);
}
if (node.rendered) {
node.$node.select(true);
}
}, this);
if (this.scrollToSelection) {
// Execute delayed because tree may be not layouted yet
setTimeout(this.revealSelection.bind(this));
}
// TODO [6.1] CGU remove this, it does way too much and renderNodeText prevents that tree can get focus when a node is clicked. It seems that it is only necessary to update the group css class
this._redecorateViewRange();
};
scout.Tree.prototype._redecorateViewRange = function() {
if (this.rendered) {
for (var i = this.viewRangeRendered.from; i < this.viewRangeRendered.to; i++) {
if (i >= this.visibleNodesFlat.length) {
break;
}
this._decorateNode(this.visibleNodesFlat[i]);
}
}
};
scout.Tree.prototype._removeSelection = function() {
// Remove children class on root nodes if no nodes were selected
if (this.selectedNodes.length === 0) {
this.nodes.forEach(function(childNode) {
if (childNode.rendered) {
childNode.$node.removeClass('child-of-selected');
}
}, this);
}
this.selectedNodes.forEach(this._removeNodeSelection, this);
};
scout.Tree.prototype._removeNodeSelection = function(node) {
if (node.rendered) {
node.$node.select(false);
}
// remove ancestor and child classes
var parentNode = node.parentNode;
if (parentNode && parentNode.rendered) {
parentNode.$node.removeClass('parent-of-selected');
}
while (parentNode && parentNode.rendered) {
parentNode.$node.removeClass('ancestor-of-selected');
parentNode = parentNode.parentNode;
}
if (node.expanded) {
node.childNodes.forEach(function(childNode) {
if (childNode.rendered) {
childNode.$node.removeClass('child-of-selected');
}
}, this);
}
};
scout.Tree.prototype._renderDropType = function() {
if (this.dropType) {
this._installDragAndDropHandler();
} else {
this._uninstallDragAndDropHandler();
}
};
scout.Tree.prototype._installDragAndDropHandler = function(event) {
if (this.dragAndDropHandler) {
return;
}
this.dragAndDropHandler = scout.dragAndDrop.handler(this, {
supportedScoutTypes: scout.dragAndDrop.SCOUT_TYPES.FILE_TRANSFER,
dropType: function() {
return this.dropType;
}.bind(this),
dropMaximumSize: function() {
return this.dropMaximumSize;
}.bind(this),
additionalDropProperties: function(event) {
var $target = $(event.currentTarget);
var properties = {
nodeId: ''
};
if ($target.hasClass('tree-node')) {
var node = $target.data('node');
properties.nodeId = node.id;
}
return properties;
}.bind(this)
});
this.dragAndDropHandler.install(this.$container, '.tree-data,.tree-node');
};
scout.Tree.prototype._uninstallDragAndDropHandler = function(event) {
if (!this.dragAndDropHandler) {
return;
}
this.dragAndDropHandler.uninstall();
this.dragAndDropHandler = null;
};
scout.Tree.prototype._updateMarkChildrenChecked = function(node, init, checked, checkChildrenChecked) {
if (!this.checkable) {
return;
}
if (checkChildrenChecked) {
var childrenFound = false;
for (var j = 0; j < node.childNodes.length > 0; j++) {
var childNode = node.childNodes[j];
if (childNode.checked || childNode.childrenChecked) {
node.childrenChecked = true;
checked = true;
childrenFound = true;
if (this.rendered && node.$node) {
node.$node
.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('children-checked', true);
}
break;
}
}
if (!childrenFound) {
node.childrenChecked = false;
if (this.rendered && node.$node) {
node.$node.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('children-checked', false);
}
}
}
if (!node.parentNode || node.parentNode.checked) {
return;
}
var stateChanged = false;
if (!checked && !init) {
//node was unchecked check siblings
var hasCheckedSiblings = false;
for (var i = 0; i < node.parentNode.childNodes.length > 0; i++) {
var siblingNode = node.parentNode.childNodes[i];
if (siblingNode.checked || siblingNode.childrenChecked) {
hasCheckedSiblings = true;
break;
}
}
if (hasCheckedSiblings !== node.parentNode.childrenChecked) {
//parentNode.checked should be false
node.parentNode.childrenChecked = hasCheckedSiblings;
stateChanged = true;
}
}
if ((checked && !node.parentNode.childrenChecked)) {
node.parentNode.childrenChecked = true;
stateChanged = true;
}
if (stateChanged) {
this._updateMarkChildrenChecked(node.parentNode, init, checked);
if (this.rendered && node.parentNode.$node) {
if (checked) {
node.parentNode.$node.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('children-checked', true);
} else {
node.parentNode.$node.children('.tree-node-checkbox')
.children('.check-box')
.toggleClass('children-checked', false);
}
}
}
};
scout.Tree.prototype._installNodeTooltipSupport = function() {
scout.tooltips.install(this.$data, {
parent: this,
selector: '.tree-node',
text: this._nodeTooltipText.bind(this),
arrowPosition: 50,
arrowPositionUnit: '%',
nativeTooltip: !scout.device.isCustomEllipsisTooltipPossible()
});
};
scout.Tree.prototype._uninstallNodeTooltipSupport = function() {
scout.tooltips.uninstall(this.$data);
};
scout.Tree.prototype._nodeTooltipText = function($node) {
var node = $node.data('node');
if (node.tooltipText) {
return node.tooltipText;
} else if (this._isTruncatedNodeTooltipEnabled() && $node.isContentTruncated()) {
return $node.children('.text').text();
}
};
scout.Tree.prototype._isTruncatedNodeTooltipEnabled = function() {
return true;
};
scout.Tree.prototype.setDisplayStyle = function(displayStyle, notifyServer) {
if (this.displayStyle === displayStyle) {
return;
}
this._renderViewportBlocked = true;
this._setProperty('displayStyle', displayStyle);
notifyServer = scout.nvl(notifyServer, true);
if (notifyServer) {
this._sendProperty('displayStyle');
}
if (displayStyle && this.selectedNodes.length > 0) {
var selectedNode = this.selectedNodes[0];
if (!selectedNode.expanded) {
this.expandNode(selectedNode);
}
}
if (this.displayStyle === scout.Tree.DisplayStyle.BREADCRUMB) {
this.addFilter(this.breadcrumbFilter, true, true);
this.filterVisibleNodes();
} else if (this.displayStyle !== scout.Tree.DisplayStyle.BREADCRUMB) {
this.removeFilter(this.breadcrumbFilter, true);
this.filter();
}
if (this.rendered) {
this._renderDisplayStyle();
}
this._renderViewportBlocked = false;
};
scout.Tree.prototype.setBreadcrumbStyleActive = function(active, notifyServer) {
if (active) {
this.setDisplayStyle(scout.Tree.DisplayStyle.BREADCRUMB, notifyServer);
} else if (!active) {
this.setDisplayStyle(scout.Tree.DisplayStyle.DEFAULT, notifyServer);
}
};
scout.Tree.prototype.isNodeInBreadcrumbVisible = function(node) {
return this._inSelectionPathList[node.id] === undefined ? false : this._inSelectionPathList[node.id];
};
scout.Tree.prototype.isBreadcrumbStyleActive = function() {
return this.displayStyle === scout.Tree.DisplayStyle.BREADCRUMB;
};
scout.Tree.prototype.setBreadcrumbTogglingThreshold = function(width) {
this.breadcrumbTogglingThreshold = width;
};
scout.Tree.prototype.expandNode = function(node, opts) {
this.setNodeExpanded(node, true, opts);
};
scout.Tree.prototype.collapseNode = function(node, opts) {
this.setNodeExpanded(node, false, opts);
};
scout.Tree.prototype.collapseAll = function() {
this.rebuildSuppressed = true;
// Collapse all expanded child nodes (only model)
this._visitNodes(this.nodes, function(node) {
this.collapseNode(node);
}.bind(this));
if (this.rendered) {
// ensure correct rendering
this._rerenderViewport();
}
this.rebuildSuppressed = false;
};
scout.Tree.prototype.setNodeExpanded = function(node, expanded, opts) {
opts = opts || {};
var lazy = scout.nvl(opts.lazy, node.lazyExpandingEnabled);
var notifyServer = scout.nvl(opts.notifyServer, true);
var renderAnimated = scout.nvl(opts.renderAnimated, true);
// Never do lazy expansion if it is disabled on the tree
if (!this.lazyExpandingEnabled) {
lazy = false;
}
if (this.isBreadcrumbStyleActive()) {
// Do not allow to collapse a selected node
if (!expanded && this.selectedNodes.indexOf(node) > -1) {
this.setNodeExpanded(node, true, opts);
return;
}
}
// Optionally collapse all children (recursively)
if (opts.collapseChildNodes) {
// Suppress render expansion
var childOpts = scout.objects.valueCopy(opts);
childOpts.renderExpansion = false;
node.childNodes.forEach(function(childNode) {
if (childNode.expanded) {
this.collapseNode(childNode, childOpts);
}
}.bind(this));
}
var renderExpansionOpts = {
expansionChanged: false,
expandLazyChanged: false
};
// Set expansion state
if (node.expanded !== expanded || node.expandedLazy !== lazy) {
renderExpansionOpts.expansionChanged = node.expanded !== expanded;
renderExpansionOpts.expandLazyChanged = node.expandedLazy !== lazy;
node.expanded = expanded;
node.expandedLazy = lazy;
if (this.groupedNodes[node.id]) {
this._updateItemPath(false, node);
}
var filterStateChanged = this._applyFiltersForNode(node);
if (filterStateChanged && renderExpansionOpts.expansionChanged) {
this._rebuildParent(node.parentNode, opts);
} else if (renderExpansionOpts.expandLazyChanged) {
node.childNodes.forEach(function(child) {
this._applyFiltersForNode(child);
}.bind(this));
}
if (node.expanded) {
this._addChildrenToFlatList(node, null, renderAnimated, null, true);
} else {
this._removeChildrenFromFlatList(node, renderAnimated);
}
if (notifyServer) {
this._send('nodeExpanded', {
nodeId: node.id,
expanded: expanded,
expandedLazy: lazy
});
}
this.viewRangeDirty = true;
}
// Render expansion
if (this.rendered && scout.nvl(opts.renderExpansion, true)) {
this._renderExpansion(node, renderExpansionOpts);
}
};
scout.Tree.prototype._rebuildParent = function(node, opts) {
if (this.rebuildSuppressed) {
return;
}
if (node.expanded || node.expandedLazy) {
this._addChildrenToFlatList(node, null, false, null, true);
} else {
this._removeChildrenFromFlatList(node, false);
}
// Render expansion
if (this.rendered && scout.nvl(opts.renderExpansion, true)) {
var renderExpansionOpts = {
expansionChanged: true
};
this._renderExpansion(node, renderExpansionOpts);
}
};
scout.Tree.prototype._removeChildrenFromFlatList = function(parentNode, animatedRemove) {
// Only if a parent is available the children are available.
if (this.visibleNodesMap[parentNode.id]) {
var parentIndex = this.visibleNodesFlat.indexOf(parentNode);
var elementsToDelete = 0;
var parentLevel = parentNode.level;
var removedNodes = [];
animatedRemove = animatedRemove && this.rendered;
if (this._$animationWrapper) {
// Note: Do _not_ use finish() here! Although documentation states that it is "similar" to stop(true, true),
// this does not seem to be the case. Implementations differ slightly in details. The effect is, that when
// calling stop() the animation stops and the 'complete' callback is executed immediately. However, when calling
// finish(), the callback is _not_ executed! (This may or may not be a bug in jQuery, I cannot tell...)
this._$animationWrapper.stop(false, true);
}
this._$expandAnimationWrappers.forEach(function($wrapper) {
$wrapper.stop(false, true);
});
for (var i = parentIndex + 1; i < this.visibleNodesFlat.length; i++) {
if (this.visibleNodesFlat[i].level > parentLevel) {
var node = this.visibleNodesFlat[i];
if (this.isHorizontalScrollingEnabled()) {
//if node is the node which defines the widest width then recalculate width for render
if (node.width === this.maxNodeWidth) {
this.maxNodeWidth = 0;
this.nodeWidthDirty = true;
}
}
delete this.visibleNodesMap[this.visibleNodesFlat[i].id];
if (node.attached && animatedRemove) {
if (!this._$animationWrapper) {
this._$animationWrapper = $('
').insertBefore(node.$node);
this._$animationWrapper.data('parentNode', parentNode);
}
if (node.isChildOf(this._$animationWrapper.data('parentNode'))) {
this._$animationWrapper.append(node.$node);
}
node.attached = false;
node.displayBackup = node.$node.css('display');
removedNodes.push(node);
} else if (node.attached && !animatedRemove) {
this.hideNode(node, false, false);
}
elementsToDelete++;
} else {
break;
}
}
this.visibleNodesFlat.splice(parentIndex + 1, elementsToDelete);
// animate closing
if (animatedRemove) { // don't animate while rendering (not necessary, or may even lead to timing issues)
this._renderViewportBlocked = true;
if (removedNodes.length > 0) {
this._$animationWrapper.animate({
height: 0
}, {
start: this.startAnimationFunc,
complete: onAnimationComplete.bind(this, removedNodes),
duration: 200,
queue: false
});
} else if (this._$animationWrapper) {
this._$animationWrapper.remove();
this._$animationWrapper = null;
onAnimationComplete.call(this, removedNodes);
} else {
this._renderViewportBlocked = false;
}
}
return removedNodes;
}
//----- Helper functions -----
function onAnimationComplete(affectedNodes) {
affectedNodes.forEach(function(node) {
node.$node.detach();
node.$node.css('display', node.displayBackup);
node.displayBackup = null;
});
if (this._$animationWrapper) {
this._$animationWrapper.remove();
this._$animationWrapper = null;
}
this.runningAnimationsFinishFunc();
}
};
scout.Tree.prototype._removeFromFlatList = function(node, animatedRemove) {
var removedNodes = [];
if (this.visibleNodesMap[node.id]) {
var index = this.visibleNodesFlat.indexOf(node);
this._removeChildrenFromFlatList(node, false);
if (this.isHorizontalScrollingEnabled()) {
//if node is the node which defines the widest width then recalculate width for render
if (node.width === this.maxNodeWidth) {
this.maxNodeWidth = 0;
this.nodeWidthDirty = true;
}
}
removedNodes = scout.arrays.ensure(this.visibleNodesFlat.splice(index, 1));
delete this.visibleNodesMap[node.id];
this.hideNode(node, animatedRemove);
}
removedNodes.push(node);
return removedNodes;
};
scout.Tree.prototype._addToVisibleFlatList = function(node, renderingAnimated) {
// if node already is in visible list don't do anything. If no parentNode is available this node is on toplevel, if a parent is available
// it has to be in visible list and also be expanded
if (!this.visibleNodesMap[node.id] && node.isFilterAccepted() && (!node.parentNode ||
(node.parentNode.expanded && this.visibleNodesMap[node.parentNode.id]))) {
if (this.initialTraversing) {
// for faster index calculation
this._addToVisibleFlatListNoCheck(node, this.visibleNodesFlat.length, renderingAnimated);
} else {
var insertIndex = this._findIndexToInsertNode(node);
this._addToVisibleFlatListNoCheck(node, insertIndex, renderingAnimated);
}
}
};
scout.Tree.prototype._findIndexToInsertNode = function(node) {
var findValidSiblingBefore = function(childNodeIndex, siblings) {
for (var i = childNodeIndex - 1; i >= 0; i--) {
if (this.visibleNodesMap[siblings[i].id]) {
return siblings[i];
}
}
// no sibling before
return null;
}.bind(this);
// function to traverse last child nodes to first child nodes of a parent.
var findLastVisibleNodeInParent = function(parent) {
if (parent.expanded) {
for (var i = parent.childNodes.length - 1; i >= 0; i--) {
if (this.visibleNodesMap[parent.childNodes[i].id]) {
return findLastVisibleNodeInParent(parent.childNodes[i]);
}
}
}
return parent;
}.bind(this);
var parentNode = node.parentNode,
siblingBefore, nodeBefore;
if (!parentNode) {
// use toplevel to find index
siblingBefore = findValidSiblingBefore(node.childNodeIndex, this.nodes);
if (!siblingBefore) {
return 0;
}
nodeBefore = findLastVisibleNodeInParent(siblingBefore);
return this.visibleNodesFlat.indexOf(nodeBefore) + 1;
} else {
siblingBefore = findValidSiblingBefore(node.childNodeIndex, node.parentNode.childNodes);
if (!siblingBefore) {
nodeBefore = parentNode;
} else {
nodeBefore = findLastVisibleNodeInParent(siblingBefore);
}
return this.visibleNodesFlat.indexOf(nodeBefore) + 1;
}
};
// TODO [6.1] CGU applies to all the add/remove to/from flat list methods:
// Is it really necessary to update dom on every operation? why not just update the list and renderViewport at the end?
// The update of the flat list is currently implemented quite complicated -> it should be simplified.
// And: because add to flat list renders all the children the rendered node count is greater than the viewRangeSize until the layout renders the viewport again -> this must not happen (can be seen when a node gets expanded=
scout.Tree.prototype._addChildrenToFlatList = function(parentNode, parentIndex, animatedRendering, insertBatch, forceFilter) {
//add nodes recursively
if (!this.visibleNodesMap[parentNode.id]) {
return 0;
}
var isSubAdding = !!insertBatch;
parentIndex = parentIndex ? parentIndex : this.visibleNodesFlat.indexOf(parentNode);
animatedRendering = animatedRendering && this.rendered; // don't animate while rendering (not necessary, or may even lead to timing issues)
if (this._$animationWrapper && !isSubAdding) {
// Note: Do _not_ use finish() here! Although documentation states that it is "similar" to stop(true, true),
// this does not seem to be the case. Implementations differ slightly in details. The effect is, that when
// calling stop() the animation stops and the 'complete' callback is executed immediately. However, when calling
// finish(), the callback is _not_ executed! (This may or may not be a bug in jQuery, I cannot tell...)
this._$animationWrapper.stop(false, true);
}
insertBatch = insertBatch ? insertBatch : this.setUpInsertBatch(parentIndex + 1);
parentNode.childNodes.forEach(function(node, index) {
var isAlreadyAdded = this.visibleNodesMap[node.id];
if (node.initialized && node.isFilterAccepted(forceFilter) && !isAlreadyAdded) {
insertBatch.insertNodes.push(node);
this.visibleNodesMap[node.id] = true;
insertBatch = this.checkAndHandleBatch(insertBatch, parentNode, animatedRendering);
if (node.expanded) {
insertBatch = this._addChildrenToFlatList(node, insertBatch.lastBatchInsertIndex(), animatedRendering, insertBatch, forceFilter);
}
} else if (node.initialized && node.isFilterAccepted(forceFilter) && isAlreadyAdded) {
this.insertBatchInVisibleNodes(insertBatch, this.viewRangeRendered.from + this.viewRangeSize >= insertBatch.lastBatchInsertIndex() && this.viewRangeRendered.from <= insertBatch.lastBatchInsertIndex(), animatedRendering);
this.checkAndHandleBatchAnimationWrapper(parentNode, animatedRendering, insertBatch);
var updateIndex = insertBatch.insertedAny() ? 2 : 1;
insertBatch = this.setUpInsertBatch(insertBatch.lastBatchInsertIndex() + updateIndex);
if (node.expanded) {
insertBatch = this._addChildrenToFlatList(node, insertBatch.lastBatchInsertIndex(), animatedRendering, insertBatch, forceFilter);
}
//do not animate following
animatedRendering = false;
}
}.bind(this));
if (!isSubAdding) {
// animation is not done yet and all added nodes are in visible range
this.insertBatchInVisibleNodes(insertBatch, this.viewRangeRendered.from + this.viewRangeSize >= insertBatch.lastBatchInsertIndex() && this.viewRangeRendered.from <= insertBatch.lastBatchInsertIndex(), animatedRendering);
this.invalidateLayoutTree();
}
return insertBatch;
};
scout.Tree.prototype.setUpInsertBatch = function(insertIndex) {
return {
insertNodes: [insertIndex, 0],
$animationWrapper: null,
lastBatchInsertIndex: function() {
if (this.insertNodes.length === 2) {
return this.insertNodes[0];
}
return this.insertNodes[0] + this.insertNodes.length - 3;
},
insertedAny: function() {
return this.insertNodes.length > 2;
}
};
};
scout.Tree.prototype.checkAndHandleBatchAnimationWrapper = function(parentNode, animatedRendering, insertBatch) {
if (animatedRendering && this.viewRangeRendered.from <= insertBatch.lastBatchInsertIndex() && this.viewRangeRendered.to >= insertBatch.lastBatchInsertIndex() && !insertBatch.$animationWrapper) {
//we are in visible area so we need a animation wrapper
//if parent is in visible area insert after parent else insert before first node.
var lastNodeIndex = insertBatch.lastBatchInsertIndex() - 1,
nodeBefore = this.viewRangeRendered.from === insertBatch.lastBatchInsertIndex() ? null : this.visibleNodesFlat[lastNodeIndex];
if (nodeBefore && lastNodeIndex >= this.viewRangeRendered.from && lastNodeIndex < this.viewRangeRendered.to && !nodeBefore.attached) {
//ensure node before is visible
this.showNode(nodeBefore, false, lastNodeIndex);
}
if (nodeBefore && nodeBefore.attached) {
insertBatch.$animationWrapper = $('
').insertAfter(nodeBefore.$node);
} else if (parentNode.attached) {
insertBatch.$animationWrapper = $('
').insertAfter(parentNode.$node);
} else if (this.$fillBefore) {
insertBatch.$animationWrapper = $('