All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nextapp.echo.extras.webcontainer.resource.Sync.RemoteTree.js Maven / Gradle / Ivy

/**
 * Component rendering peer: Tree (Remote)
 */
Extras.Sync.RemoteTree = Core.extend(Echo.Render.ComponentSync, {

    $static: {
        _BORDER_SIDE_STYLE_NAMES: ["borderTop", "borderRight", "borderBottom", "borderLeft"],

        _SIDE_TO_PADDING_MAP: { "top": "paddingTop", "right" : "paddingRight", "bottom" : "paddingBottom", "left" : "paddingLeft" },
        
        LINE_STYLE_NONE: 0,
        LINE_STYLE_SOLID: 1,
        LINE_STYLE_DOTTED: 2,
    
        _supportedPartialProperties: ["treeStructure", "selection"],
        
        TREE_IMAGES: {
            0: {
                open: "image/tree/Open.gif",
                openBottom: "image/tree/Open.gif",
                closed: "image/tree/Closed.gif",
                closedBottom: "image/tree/Closed.gif",
                join: "image/tree/Transparent.gif",
                joinBottom: "image/tree/Transparent.gif"
            },
            1: {
                vertical: "image/tree/VerticalSolid.gif",
                open: "image/tree/OpenSolid.gif",
                openBottom: "image/tree/OpenBottomSolid.gif",
                closed: "image/tree/ClosedSolid.gif",
                closedBottom: "image/tree/ClosedBottomSolid.gif",
                join: "image/tree/JoinSolid.gif",
                joinBottom: "image/tree/JoinBottomSolid.gif"
            },
            2: {
                vertical: "image/tree/VerticalDotted.gif",
                open: "image/tree/OpenDotted.gif",
                openBottom: "image/tree/OpenBottomDotted.gif",
                closed: "image/tree/ClosedDotted.gif",
                closedBottom: "image/tree/ClosedBottomDotted.gif",
                join: "image/tree/JoinDotted.gif",
                joinBottom: "image/tree/JoinBottomDotted.gif"
            } 
        }
    },
    
    $load: function() {
        Echo.Render.registerPeer("Extras.RemoteTree", this);
    },
    
    renderAdd: function(update, parentElement) {
        this._lineStyle = this.component.render("lineStyle", Extras.Sync.RemoteTree.LINE_STYLE_DOTTED);
        this._imageSet = { };
        for (var x in Extras.Sync.RemoteTree.TREE_IMAGES[this._lineStyle]) {
            this._imageSet[x] = this.client.getResourceUrl("Extras", 
                    Extras.Sync.RemoteTree.TREE_IMAGES[this._lineStyle][x]);
        }
        this._showLines = this._lineStyle != Extras.Sync.RemoteTree.LINE_STYLE_NONE;

        this._showsRootHandle = this.component.render("showsRootHandle", false);
        this._rootVisible = this.component.render("rootVisible", true);
        this._headerVisible = this.component.render("headerVisible", false);
        this._rolloverEnabled = this.component.render("rolloverEnabled");
        this._selectionEnabled = this.component.render("selectionEnabled");
        if (this._selectionEnabled) {
            this.selectionModel = new Extras.TreeSelectionModel(parseInt(this.component.get("selectionMode"), 10));
        }
        
        this._defaultInsets = this.component.render("insets");
        if (!this._defaultInsets) {
            this._defaultInsets = "0px";
        }
        this._defaultCellPadding = Echo.Sync.Insets.toCssValue(this._defaultInsets);
        
        var width = this.component.render("width");
        if (width && Core.Web.Env.QUIRK_IE_TABLE_PERCENT_WIDTH_SCROLLBAR_ERROR && Echo.Sync.Extent.isPercent(width)) {
            this._renderPercentWidthByMeasure = parseInt(width, 10);
            width = null;
        }
        
        var tableElement = document.createElement("table");
        this._element = tableElement;
        this._element.id = this.component.renderId;
        tableElement.style.borderSpacing = "0px";
        tableElement.cellSpacing = "0";
        tableElement.cellPadding = "0";
        Echo.Sync.Border.render(this.component.render("border"), tableElement);
        
        this._computeEffectBorderCompensation();
        
        if (width) {
            this._element.style.width = width;
        }
        
        var tbodyElement = document.createElement("tbody");
        tableElement.appendChild(tbodyElement);
        this._tbodyElement = tbodyElement;
        
        //-- element needed for FF-specific hack, see _doExpansion function
        this._buggerTBody = document.createElement("tbody");
        this._buggerTBody.style.display = "none";
        this._buggerRow = document.createElement("tr");
        this._buggerTBody.appendChild(this._buggerRow);
        tableElement.appendChild(this._buggerTBody);
        //--
        
        var localStructure = this.component.get("treeStructure");
        var fullLocalStructure = localStructure != null && localStructure.fullRefresh;
        if (fullLocalStructure) {
            // tree structure in local style is a full structure
            this.component.treeStructure = localStructure[0];
        } else {
            // local style structure contains a partial update
            this._mergeTreeStructureUpdate(localStructure);
        }
        this.columnCount = this.component.get("columnCount");
        
        this._renderColumnWidths();
        
        if (this._headerVisible) {
            this._renderNode(update, this.component.treeStructure.getHeaderNode());
        }
        var rootNode = this.component.treeStructure.getRootNode();
        this._renderNode(update, rootNode);
        
        parentElement.appendChild(tableElement);
    
        var selection = this.component.render("selection");
        if (selection && this._selectionEnabled) {
            this._setSelectedFromProperty(selection);
        }
    },
    
    _computeEffectBorderCompensation: function() {
        var selectionBorder = this.component.render("selectionBorder");
        var rolloverBorder = this.component.render("rolloverBorder");
        var selectionBorderLeft = 0;
        if (selectionBorder && this._selectionEnabled) {
            selectionBorderLeft = Echo.Sync.Border.getPixelSize(selectionBorder, "left");
        }
        var rolloverBorderLeft = 0;
        if (rolloverBorder && this._rolloverEnabled) {
            rolloverBorderLeft = Echo.Sync.Border.getPixelSize(rolloverBorder, "left");
        }
        this._effectBorderCompensation = Math.max(selectionBorderLeft, rolloverBorderLeft);
    },
    
    _renderColumnWidths: function() {
        if (!this.component.render("columnWidth")) {
            return;
        }
        // If any column widths are set, render colgroup.
        var columnPixelAdjustment;
        if (Core.Web.Env.QUIRK_TABLE_CELL_WIDTH_EXCLUDES_PADDING) {
            var pixelInsets = Echo.Sync.Insets.toPixels(this._defaultInsets);
            columnPixelAdjustment = pixelInsets.left + pixelInsets.right;
        }
        
        this._colGroupElement = document.createElement("colgroup");
        var renderRelative = !Core.Web.Env.NOT_SUPPORTED_RELATIVE_COLUMN_WIDTHS;
        for (var i = 0; i < this.columnCount; ++i) {
            var colElement = document.createElement("col");
            var width = this.component.renderIndex("columnWidth", i); 
            if (width != null) {
                if (Echo.Sync.Extent.isPercent(width)) {
                    colElement.width = parseFloat(width) + (renderRelative ? "*" : "%");
                } else {
                    var columnPixels = Echo.Sync.Extent.toPixels(width, true);
                    if (columnPixelAdjustment) {
                        colElement.width = columnPixels - columnPixelAdjustment + "px";
                    } else {
                        colElement.width = columnPixels + "px";
                    }
                }
            }
            this._colGroupElement.appendChild(colElement);
        }
        this._element.appendChild(this._colGroupElement);
    },
    
    renderDisplay: function() {
        if (this._renderPercentWidthByMeasure) {
            this._element.style.width = "";
            var percentWidth = (this._element.parentNode.offsetWidth * this._renderPercentWidthByMeasure) / 100;
            this._element.style.width = percentWidth + "px";
        }
    },
    
    /**
     * Creates an iterator object for easy navigating through the tree table.
     * 
     * @param startRow the row element to start with, 
     *          this element will be returned on the first call to nextRow().
     *          If null, the iteration will start at the first row.
     * @param endRow the row that ends the iteration. When endRow is encountered 
     *          while iterating the iterator will return null, and will not advance to the next row.
     */
    _elementIterator: function(startRow, endRow) {
        var component = this.component;
        if (!startRow && this._tbodyElement.firstChild) {
            startRow = this._tbodyElement.firstChild;
        }
        return {
            startRow : startRow,
            rowElement : null,
            
            /**
             * Advance to the next row. If node is provided rows will be skipped until the row is 
             * found that node is rendered to. If no row is found for the node, null is returned,
             * and the cursor is reset to its current state.
             */
            nextRow : function(node) {
                var cursor = this.rowElement;
                var result = this._nextRow();
                if (!node) {
                    return result;
                }
                var id = component.renderId + "_tr_" + node.getId();
                while (result && result.id != id) {
                    result = this._nextRow();
                }
                if (!result && node) {
                    this.rowElement = cursor;
                }
                return result;
            },
            
            _nextRow : function() {
                if (this.rowElement) {
                    if (this.rowElement.nextSibling == endRow) {
                        return null;
                    }
                    this.rowElement = this.rowElement.nextSibling;
                } else {
                    this.rowElement = this.startRow;
                }
                
                return this.rowElement;
            },
            
            /**
             * Advance to the next row, and return the node element of that row.
             */
            nextNodeElement : function() {
                this.nextRow();
                if (!this.rowElement) {
                    return null;
                }
                return this.currentNodeElement();
            },
            
            /**
             * Returns the node element on the current row.
             */
            currentNodeElement : function() {
                var cellElement = this._nestedTdElement(this.rowElement);
                while (cellElement) {
                    if (cellElement.__ExtrasTreeCellType == "node") {
                        return cellElement;
                    }
                    cellElement = cellElement.nextSibling;
                }
                return null;
            },
    
            /**
             * Returns the expando element on the current row, if the current
             * row does not contain an expando element, null is returned.
             */
            currentExpandoElement : function() {
                var cellElement = this._nestedTdElement(this.rowElement);
                while (cellElement) {
                    if (cellElement.__ExtrasTreeCellType == "expando") {
                        return cellElement;
                    }
                    cellElement = cellElement.nextSibling;
                }
                return null;
            },
            
            _nestedTdElement : function(rowElement) {
                var count = 0;
                var e = rowElement;
                do {
                    e = e.firstChild;
                    if (e.tagName.toLowerCase() == "td") {
                        ++count;
                    }
                } while (count < 2);
                return e;
            }
        };
    },
    
    _renderNode: function(update, node) {
        var rowElement = this._getRowElementForNode(node);
        var nodeDepth = this.component.treeStructure.getNodeDepth(node);
        
        var insertBefore = null;
        if (rowElement) {
            insertBefore = rowElement.nextSibling;
        }
    
        var nodeSibling = this.component.treeStructure.getNodeNextSibling(node, true);
        var endRow = null;
        if (nodeSibling) {
            endRow = this._getRowElementForNode(nodeSibling);
        }
        var iterator = this._elementIterator(rowElement, endRow);
        var visible = true;
        var parentNode = this.component.treeStructure.getNode(node.getParentId());
        if (parentNode) {
            visible = parentNode.isExpanded();
        }
        this._renderNodeRecursive(update, node, iterator, nodeDepth, insertBefore, visible);
    },
    
    _renderNodeRecursive: function(update, node, iterator, depth, insertBefore, visible) {
        if (visible == null) {
            visible = true;
        }
        if (!this._rootVisible && node == this.component.treeStructure.getRootNode()) {
            visible = false;
        }
        var trElement = iterator.nextRow(node);
        var tdElement;
        var expandoElement;
        
        var rendered = trElement != null;
        
        if (!rendered) {
            var elems = this._renderNodeRowStructure(insertBefore, node, depth);
            // skip the just created row element
            iterator.nextRow();
            trElement = elems.trElement;
            tdElement = elems.tdElement;
            expandoElement = elems.expandoElement;
            
            var component = this.component.application.getComponentByRenderId(node.getId());
            Echo.Render.renderComponentAdd(update, component, tdElement);
            
            if (this.columnCount > 1) {
                for (var c = 0; c < this.columnCount - 1; ++c) {
                    var columnElement = document.createElement("td");
                    
                    var columnComponent = this.component.application.getComponentByRenderId(node.getColumn(c));
                    Echo.Render.renderComponentAdd(update, columnComponent, columnElement);
                    
                    trElement.appendChild(columnElement);
                }
            }
            this._setDefaultRowStyle(trElement);
        } else {
            trElement.style.display = ""; // unhide
            tdElement = iterator.currentNodeElement();
            expandoElement = iterator.currentExpandoElement();
        }
    
        if (expandoElement) {
            this._renderExpandoElement(node, expandoElement);
        }
        if (!visible) {
            trElement.style.display = "none";
        }
        
        // render child nodes
        var expanded = node.isExpanded();    
        var childCount = node.getChildNodeCount();
        insertBefore = trElement.nextSibling;
        for (var i = 0; i < childCount; ++i) {
            var childNode = node.getChildNode(i);
            if (insertBefore) {
                if (insertBefore.id == (this.component.renderId + "_tr_" + childNode.getId())) {
                    insertBefore = insertBefore.nextSibling;
                }
            }
            if (expanded || !rendered) {
                this._renderNodeRecursive(update, childNode, iterator, depth + 1, insertBefore, expanded);
            } else {
                // child node should not be visible
                this._hideNode(childNode, iterator);
            }
        }
    },
    
    _getIconLineStyleSuffix: function() {
        switch (this._lineStyle) {
            case Extras.Sync.RemoteTree.LINE_STYLE_NONE:
                return "";
            case Extras.Sync.RemoteTree.LINE_STYLE_SOLID:
                return "Solid";
            case Extras.Sync.RemoteTree.LINE_STYLE_DOTTED:
                return "Dotted";
        }
    },
    
    _getToggleIcon: function(node) {
        var imageSuffix = this._getIconLineStyleSuffix();
        var bottom = "";
        if (this._showLines && !this.component.treeStructure.hasNodeNextSibling(node)) {
            bottom = "Bottom";
        }
        if (node.isExpanded()) {
            return this.component.render("nodeOpen" + bottom + "Icon", this._imageSet["open" + bottom]);
        } else {
            return this.component.render("nodeClosed" + bottom + "Icon", this._imageSet["closed" + bottom]);
        }
    },
    
    _getJoinIcon: function(node) {
        var imageSuffix = this._getIconLineStyleSuffix();
        var bottom = "";
        if (!this.component.treeStructure.hasNodeNextSibling(node)) {
            bottom = "Bottom";
        }
        return this.component.render("lineJoin" + bottom + "Icon", this._imageSet["join" + bottom]);
    },
    
    _renderExpandoElement: function(node, expandoElement) {
        if (node.isLeaf()) {
            var joinIcon = this._getJoinIcon(node);
            var joinFillImage = { url: joinIcon.url ? joinIcon.url : joinIcon, repeat: "no-repeat", x: "50%", y: 0 };
            Echo.Sync.FillImage.render(joinFillImage, expandoElement);
        } else {
            var toggleIcon = this._getToggleIcon(node);
            var toggleFillImage = { url: toggleIcon.url ? toggleIcon.url : toggleIcon, repeat: "no-repeat", x: "50%", y: 0 };
            Echo.Sync.FillImage.render(toggleFillImage, expandoElement);
        }
    },
    
    _hideNode: function(node, iterator) {
        var rowElement = iterator.nextRow(node);
        if (!rowElement || rowElement.style.display == "none") {
            return;
        }
        rowElement.style.display = "none";
        var childCount = node.getChildNodeCount();
        for (var i = 0; i < childCount; ++i) {
            var childNode = node.getChildNode(i);
            this._hideNode(childNode, iterator);
        }
    },
        
    /**
     * Renders border to element, only the sides provided in the sides argument will be applied.
     * 

* If border is null, this method returns silently. * * @param border the border to render * @param {Array} side names to render, (borderLeft/borderRight/borderTop/borderBottom) * The elements of the array need not be ordered. * @param element the element to render border to */ _applyBorder: function(border, sides, element) { if (!border) { return; } for (var i in sides) { Echo.Sync.Border.render(border, element, sides[i]); } }, /** * Renders insets to element, only the sides provided in the sides argument will be applied. * * @param {String} insets the insets to render, may not be null * @param {Array} sides the indices of the insets sides to render, possible values are: *

    *
  • top
  • *
  • right
  • *
  • bottom
  • *
  • left
  • *
* The elements of the array need not be ordered. * @param element the element to render insets to */ _applyInsets: function(insets, sides, element) { var pixelInsets = Echo.Sync.Insets.toPixels(insets); for (var i = 0; i < sides.length; ++i) { var padding = Extras.Sync.RemoteTree._SIDE_TO_PADDING_MAP[sides[i]]; element.style[padding] = pixelInsets[sides[i]] + "px"; } }, /** * Creates the row structure for node. The resulting row will be inserted in the current table element * (this._element) before insertBefore. If insertBefore is null, the row will be appended to the end * of the table. * * @param {HTMLTableRowElement} insertBefore the row element to insert the resulting row before, if null * the resulting row will be appended to the end of the table * @param {Extras.RemoteTree.TreeNode} node the node to create the row structure for * @param {Integer} depth the depth of this node, the root node has depth 1 * * @return an object containing three elements that were created in this method. The object * the following elements: *
    *
  • trElement (the row element)
  • *
  • tdElement (the cell element in which the node component is rendered (column 0))
  • *
  • expandoElement (the cell element in which the expando icon is rendered, * this element is null for the header row)
  • *
* @type Object */ _renderNodeRowStructure: function(insertBefore, node, depth) { var img; var isHeader = node == this.component.treeStructure.getHeaderNode(); var trElement = document.createElement("tr"); trElement.id = this.component.renderId + "_tr_" + node.getId(); trElement.style.cursor = isHeader || !this._selectionEnabled ? "default" : "pointer"; trElement.style.verticalAlign = "top"; var nodeTable = document.createElement("table"); nodeTable.style.borderCollapse = "collapse"; nodeTable.style.cellPadding = "0px"; nodeTable.style.cellSpacing = "0px"; nodeTable.style.padding = "0px"; nodeTable.appendChild(document.createElement("tbody")); var nodeRowElement = document.createElement("tr"); if (!this._rootVisible || (!this._showsRootHandle && node != this.component.treeStructure.getRootNode())) { --depth; } var parentNode = this.component.treeStructure.getNode(node.getParentId()); for (var c = 0; c < depth - 1; ++c) { var rowHeaderElement = document.createElement("td"); rowHeaderElement.id = "tree_" + node.getId() + "_" + c; rowHeaderElement.style.padding = "0px"; rowHeaderElement.style.width = "19px"; img = document.createElement("img"); img.src = this.client.getResourceUrl("Extras", "image/tree/Transparent.gif"); img.style.width = "19px"; // img.style.height = "10px"; rowHeaderElement.appendChild(img); if (parentNode) { if (this._showLines && this.component.treeStructure.hasNodeNextSibling(parentNode) && this._imageSet.vertical) { var verticalLineFillImage = { url: this._imageSet.vertical, repeat: "no-repeat", x: "50%", y: 0 }; Echo.Sync.FillImage.render(verticalLineFillImage, rowHeaderElement); } parentNode = this.component.treeStructure.getNode(parentNode.getParentId()); } nodeRowElement.insertBefore(rowHeaderElement, nodeRowElement.firstChild); } var expandoElement; if (!isHeader && !(!this._showsRootHandle && node == this.component.treeStructure.getRootNode())) { expandoElement = document.createElement("td"); expandoElement.id = "tree_" + node.getId() + "_expando"; expandoElement.__ExtrasTreeCellType = "expando"; expandoElement.style.padding = "0"; expandoElement.style.width = "19px"; expandoElement.style.textAlign = "center"; img = document.createElement("img"); img.src = this.client.getResourceUrl("Extras", "image/tree/Transparent.gif"); img.style.width = "19px"; // img.style.height = "10px"; expandoElement.appendChild(img); nodeRowElement.appendChild(expandoElement); } var tdElement = document.createElement("td"); tdElement.style.padding = "0px"; trElement.appendChild(tdElement); var nodeCellElement = document.createElement("td"); nodeCellElement.__ExtrasTreeCellType = "node"; nodeRowElement.appendChild(nodeCellElement); nodeTable.firstChild.appendChild(nodeRowElement); tdElement.appendChild(nodeTable); tdElement.style.overflow = "hidden"; trElement.firstChild.style.paddingLeft = this._effectBorderCompensation + "px"; this._tbodyElement.insertBefore(trElement, insertBefore); var elements = { trElement: trElement, tdElement: nodeCellElement, expandoElement: expandoElement }; if (!isHeader) { this._addEventListeners(elements); } return elements; }, /** * Applies the default style for rowElement. This method renders the following css properties: *
    *
  • foreground
  • *
  • background
  • *
  • backgroundImage
  • *
  • border
  • *
  • font
  • *
* * @param {HTMLTableRowElement} rowElement the row element to apply the style on */ _setDefaultRowStyle: function(rowElement) { // HACKHACK this._setRolloverState(rowElement, false); }, _resolvePropertyName: function(effect, propName, state) { if (effect && state) { return effect + propName.charAt(0).toUpperCase() + propName.substring(1); } else { return propName.charAt(0).toLowerCase() + propName.substring(1); } }, _getProperty: function(propName, context, layoutData, onlyEffectProps) { var result; var effect = context.getDefaultEffect(); var resolvedName = this._resolvePropertyName(effect, propName, false); while (!result && effect) { var state = context.isEffect(effect); var resolvedEffectName = this._resolvePropertyName(effect, propName, state); if (state) { result = this.component.render(resolvedEffectName); } effect = context.getEffect(effect); } if (!result && layoutData) { result = layoutData[resolvedName]; } if (!result && !onlyEffectProps) { result = this.component.render(resolvedName); } return result; }, /** * Creates a context object for rendering row styles. * * @param {HTMLTableRowElement} rowElement the row element to apply the style on * @param {String} effect the most significant effect (the effect that should be * rendered first, before trying the other effects) * @param {Boolean} state the effect state, true if the effect should be rendered, false if not */ _createRowStyleContext: function(rowElement, effect, state) { var context = { rowElement: rowElement, effects: {}, effectOrder: [], /** * Returns the default effect (the first effect of which the state is enabled) */ getDefaultEffect: function() { for (var i = 0; i < this.effectOrder.length; i++) { var effect = this.effectOrder[i]; if (this.isEffect(effect)) { return effect; } } return null; }, /** * Gets the effect that should be applied after the given effect */ getEffect: function(effect) { var index; if (effect) { index = Core.Arrays.indexOf(this.effectOrder, effect) + 1; } else { index = 0; } return this.effectOrder[index]; }, /** * Checks if the given effect is enabled */ isEffect: function(effect) { return this.effects[effect]; }, /** * Add an affect */ addEffect: function(effect, state) { this.effectOrder.push(effect); this.effects[effect] = state; } }; if (effect) { context.addEffect(effect, state == null ? true : state); } return context; }, /** * Sets the style for rowElement. This method renders the following css properties: *
    *
  • foreground
  • *
  • background
  • *
  • backgroundImage
  • *
  • border
  • *
  • font
  • *
* * @param {Object} context a context object created with #_createRowStyleContext() */ _setRowStyle: function(context) { var node = this._getNodeFromElement(context.rowElement); var nodeComponent = this.component.application.getComponentByRenderId(node.getId()); var nodeLayout = nodeComponent.render("layoutData"); var effect = context.getDefaultEffect(); var index = -1; var cellElement = context.rowElement.firstChild; var visitedNodeCell = false; while (cellElement) { visitedNodeCell = index > -1; var columnLayout; if (index > -1) { var columnComponent = this.component.application.getComponentByRenderId(node.getColumn(index)); columnLayout = columnComponent.render("layoutData"); } else { this._renderNodeCellInsets(cellElement, nodeLayout); } var layout = visitedNodeCell ? columnLayout : nodeLayout; var foreground = this._getProperty("foreground", context, layout); var background = this._getProperty("background", context, layout); var backgroundImage = this._getProperty("backgroundImage", context, layout); var border = this._getProperty("border", context, layout, true); Echo.Sync.Color.renderClear(foreground, cellElement, "color"); Echo.Sync.Color.renderClear(background, cellElement, "backgroundColor"); Echo.Sync.FillImage.renderClear(backgroundImage, cellElement); if (visitedNodeCell) { var insets; if (columnLayout) { insets = columnLayout.insets; } else { insets = this._defaultInsets; } Echo.Sync.Insets.render(insets, cellElement, "padding"); } ++index; var font = this.component.render(this._resolvePropertyName(effect, "font", true)); if (font || !effect) { Echo.Sync.Font.renderClear(null, cellElement); if (font) { Echo.Sync.Font.renderClear(font, cellElement); } } // prevent text decoration for spacing cells, otherwise the nbsp will show up (underlined or striked through) if (!visitedNodeCell) { cellElement.style.textDecoration = "none"; } if (!cellElement.firstChild) { cellElement.appendChild(document.createTextNode("\u00a0")); } cellElement = cellElement.nextSibling; } this._renderRowBorder(context); }, /** * Renders a border to a whole row. Mimicks border collapse, setting border-collapse to collapse * causes bugs in Firefox, and the border would add to the total size of the table. */ _renderRowBorder: function(context, override) { if (!this._effectBorderRows) { this._effectBorderRows = new Core.Arrays.LargeMap(); } var effectBorder = this._getProperty("border", context, null, true); var defaultBorder = this.component.render("border"); var hadEffect = this._effectBorderRows.map[context.rowElement.id]; if (effectBorder) { var node = this._getNodeFromElement(context.rowElement); this._effectBorderRows.map[context.rowElement.id] = true; } else { if (hadEffect) { this._effectBorderRows.remove(context.rowElement.id); } } var prevRowHasEffect; if (context.rowElement.previousSibling) { prevRowHasEffect = this._effectBorderRows.map[context.rowElement.previousSibling.id]; } if (!prevRowHasEffect && context.rowElement.previousSibling && (effectBorder || hadEffect)) { var prevRowContext = this._createRowStyleContext(context.rowElement.previousSibling); this._renderRowBorder(prevRowContext, effectBorder != null); } var cellE = context.rowElement.firstChild; while (cellE) { Echo.Sync.Border.renderClear(null, cellE); this._renderBorder(cellE, defaultBorder, true, false, override); this._renderBorder(cellE, effectBorder, false, true); cellE = cellE.nextSibling; } var compensation = this._effectBorderCompensation; if (effectBorder) { var currentCompensation = Echo.Sync.Border.getPixelSize(effectBorder, "left"); if (currentCompensation === 0) { compensation = 0; } else { compensation -= currentCompensation; } } context.rowElement.firstChild.style.paddingLeft = compensation + "px"; }, _renderBorder: function(cellElement, border, renderCellBorders, override, overrideBottom) { var sides = []; if (override || cellElement.parentNode.previousSibling) { sides.push("borderTop"); } if (override || overrideBottom) { sides.push("borderBottom"); } if (override && !cellElement.previousSibling) { sides.push("borderLeft"); } if ((override && !cellElement.nextSibling) || (renderCellBorders && cellElement.nextSibling)) { sides.push("borderRight"); } this._applyBorder(border, sides, cellElement); }, _renderNodeCellInsets: function(cellElement, nodeLayout) { // Special handling for the first cell in a row (the 'node cell') // because the node is rendered as a table within the td var insets; if (nodeLayout) { insets = nodeLayout.insets; } else { insets = this._defaultInsets; } var subRow = cellElement.firstChild.firstChild.firstChild; // cellElement.table.tbody.tr var subCell = subRow.firstChild; while (subCell) { // don't render insets on expando cell, would result in gapped lines if (subCell.__ExtrasTreeCellType != "expando") { if (subCell == subRow.firstChild && subCell == subRow.lastChild) { this._applyInsets(insets, ["top", "right", "left", "bottom"], subCell); } else if (subCell == subRow.firstChild) { this._applyInsets(insets, ["top", "bottom", "left"], subCell); } else if (subCell != subRow.lastChild) { this._applyInsets(insets, ["top", "bottom"], subCell); } else if (subCell == subRow.lastChild) { this._applyInsets(insets, ["top", "bottom", "right"], subCell); } } subCell = subCell.nextSibling; } }, /** * Sets the selection state for the given node. * * @param {Extras.RemoteTree.TreeNode} node the node to set the selection state for * @param {Boolean} selectionState the new selection state of node * @param {HTMLTableRowElement} rowElement (optional) the row element node is rendered to, * if not provided this method will look it up automatically. */ _setSelectionState: function(node, selectionState, rowElement) { if (!rowElement) { rowElement = this._getRowElementForNode(node); } this.selectionModel.setSelectionState(node, selectionState); var context = this._createRowStyleContext(rowElement, "selection", selectionState); this._setRowStyle(context); }, /** * Deselects all selected rows. */ _clearSelected: function() { var selected = this.selectionModel.getSelectedNodes(); while (selected.length > 0) { this._setSelectionState(selected[0], false); } }, /** * Sets the selection state based on the given selection property value. * * @param {String} value the value of the selection property * @param {Boolean} clearPrevious if the previous selection state should be overwritten */ _setSelectedFromProperty: function(value, clearPrevious) { var selectedIds = value.split(","); if (this.selectionModel.equalsSelectionIdArray(selectedIds)) { return; } if (clearPrevious) { this._clearSelected(); } for (var i = 0; i < selectedIds.length; i++) { if (selectedIds[i] === "") { continue; } var node = this.component.treeStructure.getNode(selectedIds[i]); this._setSelectionState(node, true); } }, /** * Renders the rollover state for the given row element. If rolloverState is false, * and the node is selected, the selected state will be rendered. * * @param {HTMLTableRowElement} rowElement the element to render the rollover state to * @param {Boolean} rolloverState true if the rollover state should be rendered, false * for the default (or selection) state */ _setRolloverState: function(rowElement, rolloverState) { var node = this._getNodeFromElement(rowElement); var context = this._createRowStyleContext(rowElement, "rollover", rolloverState); if (this._selectionEnabled && this.selectionModel.isNodeSelected(node)) { context.addEffect("selection", true); } this._setRowStyle(context); }, /** * Gets the node that is rendered to the given element. * If the given element is not a tr, this method will walk upwards through the * hierarchy until a tr element is found, it will then use that element * to find the node rendered to that tr. */ _getNodeFromElement: function(element) { var id = element.id; var nodeId; if (id.indexOf("_tr_") == -1) { var e = element; while ((e = e.parentNode)) { if (e.nodeName.toLowerCase() == "tr") { return this._getNodeFromElement(e); } } } else { nodeId = id.substring(id.indexOf("_tr_") + 4); } return this.component.treeStructure.getNode(nodeId); }, /** * Gets the row element the node is rendered to. * * @param {Extras.RemoteTree.TreeNode} node the node to get the row element for * * @return the row element * @type HTMLTableRowElement */ _getRowElementForNode: function(node) { var testId = this.component.renderId + "_tr_" + node.getId(); var rowElement = document.getElementById(testId); if (rowElement) { return rowElement; } // the table element is not yet added to the dom structure, iterate over the rows. var iterator = this._elementIterator(); rowElement = iterator.nextRow(); while (rowElement) { if (rowElement.id == testId) { return rowElement; } rowElement = iterator.nextRow(); } return null; }, /** * Gets the visible row index of node. If node is not visible, -1 is returned. * * @param {Extras.RemoteTree.TreeNode} node the node to get the row index for * * @return the row index * @type Integer */ _getRowIndexForNode: function(node) { var testElement = this._tbodyElement.firstChild; var index = this._headerVisible ? -1 : 0; while (testElement) { if (testElement.id == this.component.renderId + "_tr_" + node.getId()) { if (index != -1 && !this._rootVisible && this._headerVisible) { ++index; } return index; } testElement = testElement.nextSibling; if (testElement.style.display != "none") { // non-expanded nodes should not be taken into account ++index; } } return null; }, /** * Gets the visible row index of element. If element is not visible, -1 is returned. * * @param {HTMLTableRowElement} element the row element to get the row index for * * @return the row index * @type Integer */ _getRowIndex: function(element) { if (element.style.display == "none") { return null; } var testElement = this._tbodyElement.firstChild; var index = this._headerVisible ? -1 : 0; while (testElement) { if (testElement == element) { if (index != -1 && !this._rootVisible && this._headerVisible) { ++index; } return index; } testElement = testElement.nextSibling; if (testElement.style.display != "none") { // non-expanded nodes should not be taken into account ++index; } } return null; }, _addEventListeners: function(elements) { var expansionRef = Core.method(this, this._expansionHandler); var selectionRef = Core.method(this, this._selectionHandler); if (this._selectionEnabled) { Core.Web.Event.add(elements.trElement, "click", selectionRef, false); Core.Web.Event.Selection.disable(elements.trElement); } if (elements.expandoElement) { Core.Web.Event.add(elements.expandoElement, "click", expansionRef, false); } Core.Web.Event.add(elements.tdElement, "click", expansionRef, false); if (this._selectionEnabled || this._rolloverEnabled) { var mouseEnterLeaveSupport = Core.Web.Env.PROPRIETARY_EVENT_MOUSE_ENTER_LEAVE_SUPPORTED; var enterEvent = mouseEnterLeaveSupport ? "mouseenter" : "mouseover"; var exitEvent = mouseEnterLeaveSupport ? "mouseleave" : "mouseout"; var rolloverEnterRef = Core.method(this, this._processRolloverEnter); var rolloverExitRef = Core.method(this, this._processRolloverExit); if (this._rolloverEnabled) { Core.Web.Event.add(elements.trElement, enterEvent, rolloverEnterRef, false); Core.Web.Event.add(elements.trElement, exitEvent, rolloverExitRef, false); } } }, _doExpansion: function(node, e) { if (node.isLeaf()) { return false; } if (node.isExpanded() && e.registeredTarget.__ExtrasTreeCellType == "node") { // only collapse when the expando element is clicked // this behavior is consistent with at least Windows Explorer and qooxdoo tree return false; } if (node.isExpanded()) { node.setExpanded(false); // no other peers will be called, so update may be null this._renderNode(null, node); } else if (node.getChildNodeCount() > 0) { // we already have the children of this node, expand and render direct node.setExpanded(true); // no other peers will be called, so update may be null this._renderNode(null, node); } // Hack to make sure FF renders the whole table after expanding / collapsing a node if (Core.Web.Env.BROWSER_FIREFOX && Core.Web.Env.BROWSER_VERSION_MAJOR == 3 && Core.Web.Env.BROWSER_VERSION_MINOR === 0) { var elem = this._buggerTBody; var oldDisplay = elem.style.display; elem.style.display = ""; setTimeout(function() { elem.style.display = oldDisplay; }, 0); } // hack ends here var rowIndex = this._getRowIndexForNode(node); // make sure the property is sent to the server, it might be better to use an action for this // but we also want to send a selection of the just expanded node. this.component.set("expansion", -1); this.component.set("expansion", rowIndex); return true; }, _doSelection: function(node, e) { var trElement = this._getRowElementForNode(node); var rowIndex = this._getRowIndexForNode(node); Core.Web.DOM.preventEventDefault(e); var update = new Extras.RemoteTree.SelectionUpdate(); var specialKey = e.shiftKey || e.ctrlKey || e.metaKey || e.altKey; if (!this.selectionModel.isSelectionEmpty() && (this.selectionModel.isSingleSelection() || !(specialKey))) { update.clear = true; this._clearSelected(); } if (!this.selectionModel.isSingleSelection() && e.shiftKey && this.lastSelectedNode) { if (this.lastSelectedNode.equals(node)) { return; } var startNode; var endNode; var lastSelectedIndex = this._getRowIndexForNode(this.lastSelectedNode); if (lastSelectedIndex < rowIndex) { startNode = this.lastSelectedNode; endNode = node; } else { startNode = node; endNode = this.lastSelectedNode; } var iterator = this.component.treeStructure.iterator(startNode, false, endNode); var i = lastSelectedIndex < rowIndex ? lastSelectedIndex : rowIndex; trElement = this._getRowElementForNode(startNode); while (iterator.hasNext()) { node = iterator.nextNode(); this._setSelectionState(node, true, trElement); update.addSelection(i++); do { trElement = trElement.nextSibling; } while (trElement && trElement.style.display == "none"); } } else { this.lastSelectedNode = node; var selected = !this.selectionModel.isNodeSelected(node); if (selected || !update.clear) { this._setSelectionState(node, selected, trElement); } if (selected) { update.addSelection(rowIndex); } else if (!update.clear) { update.removeSelection(rowIndex); } } this.component.set("selectionUpdate", update); return true; }, _expansionHandler: function(e) { if (!this.client || !this.client.verifyInput(this.component)) { return; } var node = this._getNodeFromElement(e.registeredTarget); this._doExpansion(node, e); var type = e.registeredTarget.__ExtrasTreeCellType; if (this._selectionEnabled && type && type != "expando") { this._doSelection(node, e); } this.component.doAction(); return false; }, _selectionHandler: function(e) { if (!this.client || !this.client.verifyInput(this.component)) { return; } var node = this._getNodeFromElement(e.registeredTarget); this._doSelection(node, e); this.component.doAction(); return false; }, _processRolloverEnter: function(e) { if (!this.client || !this.client.verifyInput(this.component) || Core.Web.dragInProgress) { return; } this._setRolloverState(e.registeredTarget, true); }, _processRolloverExit: function(e) { if (!this.client || !this.client.verifyInput(this.component)) { return; } this._setRolloverState(e.registeredTarget, false); }, renderDispose: function(update) { //FIXME this might blow up performance, maybe cache all elements that have a click listener, // but that will probably blow memory usage... var it = this._elementIterator(); var row; while ((row = it.nextRow())) { Core.Web.Event.removeAll(row); var e = it.currentNodeElement(); if (e) { Core.Web.Event.removeAll(e); } e = it.currentExpandoElement(); if (e) { Core.Web.Event.removeAll(e); } } this._buggerTBody = null; this._buggerRow = null; this._effectBorderRows = null; this._prevMaxDepth = null; //this.component.treeStructure = null; this._tbodyElement = null; this._element = null; }, renderUpdate: function(update) { var propertyNames = update.getUpdatedPropertyNames(); // remove properties that are only changed on the client Core.Arrays.remove(propertyNames, "expansion"); Core.Arrays.remove(propertyNames, "selectionUpdate"); if (propertyNames.length === 0 && !update.getRemovedChildren()) { return false; } // end of the hack var treeStructureUpdate = update.getUpdatedProperty("treeStructure"); var fullStructure = (treeStructureUpdate && treeStructureUpdate.newValue && treeStructureUpdate.newValue.fullRefresh); if (!fullStructure) { // removal of children indicates that the tree was invalidated, // and thus all components are re-rendered, and the tree structure we have at the client // is no longer valid. if (treeStructureUpdate && treeStructureUpdate.newValue) { // tree structure updates are always partial, even when there are other updates we can't handle this._renderTreeStructureUpdate(treeStructureUpdate.newValue, update); } if (Core.Arrays.containsAll(Extras.Sync.RemoteTree._supportedPartialProperties, propertyNames, true)) { var selection = update.getUpdatedProperty("selection"); if (selection && this._selectionEnabled) { this._setSelectedFromProperty(selection.newValue, true); } // partial update return false; } } else { // we have a full structure property sent down from the server, reset current structure this.component.treeStructure = null; } var element = this._element; var containerElement = element.parentNode; Echo.Render.renderComponentDispose(update, update.parent); containerElement.removeChild(element); this.renderAdd(update, containerElement); return true; }, _mergeTreeStructureUpdate: function(treeStructureUpdate) { var nodes = []; var structs = treeStructureUpdate; for (var i = 0; i < structs.length; ++i) { var struct = structs[i]; var updateRootNode = struct.getRootNode(); var node = this.component.treeStructure.getNode(updateRootNode.getId()); if (node) { this.component.treeStructure.addOrUpdateChildNodes(updateRootNode); node.setExpanded(updateRootNode.isExpanded()); } else { node = this.component.treeStructure.getNode(updateRootNode.getParentId()); node.setExpanded(true); this.component.treeStructure.addOrUpdateNode(updateRootNode); } nodes.push(node); } return nodes; }, _renderTreeStructureUpdate: function(treeStructureUpdate, update) { var nodes = this._mergeTreeStructureUpdate(treeStructureUpdate); for (var i = 0; i < nodes.length; ++i) { var node = nodes[i]; this._renderNode(update, node); } } });




© 2015 - 2025 Weber Informatics LLC | Privacy Policy