Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Possible flaw in __add function, adding grandchildren to node.

    SmartClient Version: v9.1p_2014-07-12/Pro Deployment (built 2014-07-12)

    Context TreeGrid/Tree

    Hello,

    In my case I have a tree that is built from a datasource. Because I use the parent model the root node is not represented in the datasource so I create this root artificially. Now there is a situation where the user can add a parent to this artificial root. In this situation the 'old' rootnode becomes the new artificial rootnode and the new object (created by the user) is put under this artificial rootnode. Because of this I need to move the children of the artificial rootnode under the new object, so I change the parentpointer of the children to the id of this new object.

    Anyway when I reconstruct the tree with the modified data and do setData on the TreeGrid the children are still attached to this rootnode (one level too high) I found that this happens in the _add method under ISC_Grids.

    __add in ISC_Grids:
    Code:
    __add : function (node, parent, position) {
        var pagedResultTree = (
                isc.ResultTree != null && isc.isA.ResultTree(this) && this.isPaged());
    
    ...
    
     var grandChildren = (pagedResultTree
                ? this._canonicalizeChildren(node, info.grandChildren, false) : info.grandChildren);
    
        if (pagedResultTree) {
            var fromParent = (parent[this.canReturnOpenSubfoldersProperty] != null),
                openSubfoldersAllowed = (fromParent ?
                    parent[this.canReturnOpenSubfoldersProperty] : this.canReturnOpenFolders);
    
            if (!openSubfoldersAllowed &&
                this.isOpen(node) &&
                grandChildren != null && !grandChildren.isEmpty())
            {
                this.logWarn(
                    "Adding the open folder node '" + this.getPath(node) + "' as a child of the " +
                    "parent node '" + this.getPath(parent) + "' is contradictory to the setting " +
                    "of the " + (fromParent ? "'" + this.canReturnOpenSubfoldersProperty + "' " +
                    "property of the parent node." : "'canReturnOpenFolders' property of the tree."));
            }
        }
    
        var deltaLength = 0;
        if (pagedResultTree && isc.isA.ResultSet(grandChildren)) {
            if (!(grandChildren.lengthIsKnown() && grandChildren.allMatchingRowsCached())) {
                this._setVisibleDescendantsCached(node, false, parent, false);
            }
        } else if (grandChildren != null) {
            // If the node has children, recursively add them to the node.  This ensures that
            // their parent link is set up correctly.
    
            // Handle children being specified as a single element recursively.
            // _add will slot the element into the new children array.
            if (!isc.isAn.Array(grandChildren)) {
                this.__add(grandChildren, node);
            } else if (grandChildren.length > 0) {
                this.__addList(grandChildren, node);
            }
    In this method you can see that the grandChildren are added to the node through __addList. In my case if the artificial node is passed through this method than it will put the old children in the grandChildren parameter and so they are added to the root. Could it be that this method does not take any changes to the parentpointers in account? Or am I forgetting to modify something on one of the nodes?

    Kind Regards,
    Steven

    #2
    Our best guess is that you are getting into this situation because you are re-using the same object for the "root" property of both Trees, which would be invalid, since the Tree maintains an array of children with each node. When the root from the old Tree is used as the root for the new Tree, the old children of the root node will still be listed as children and will remain attached to that root node.

    Instead, set the new root of the Tree to a clean copy of the artificial root node (using DataSource.copyRecord()) or omit the root property and instead specify Tree.rootValue so that Tree will auto-generate distinct, artificial root nodes.

    The following code demonstrates that your use case works without issue when a new root node is created as described.

    Code:
    isc.DataSource.create({
        ID: "testDS",
        fields: [
            { name: "EmployeeId", type: "sequence", primaryKey: true },
            { name: "ReportsTo", type: "integer", foreignKey: "EmployeeId" },
            { name: "Name", type: "text" }],
        clientOnly: true,
        cacheData: [
            { EmployeeId: 182, ReportsTo: 1, Name: "Tamara Kane" },
            { EmployeeId: 183, ReportsTo: 1, Name: "Joan Little" },
            { EmployeeId: 184, ReportsTo: 1, Name: "Kirill Amirov" },
            { EmployeeId: 185, ReportsTo: 1, Name: "Rui Shu" },
            { EmployeeId: 186, ReportsTo: 1, Name: "John Garrison" },
            { EmployeeId: 187, ReportsTo: 1, Name: "Abigail Lippman" },
            { EmployeeId: 188, ReportsTo: 1, Name: "Rogine Leger" },
            { EmployeeId: 189, ReportsTo: 1, Name: "Gene Porter" },
            { EmployeeId: 265, ReportsTo: 189, Name: "Olivier Doucet" },
            { EmployeeId: 264, ReportsTo: 189, Name: "Cheryl Pearson" },
            { EmployeeId: 190, ReportsTo: 1, Name: "Carol Finley" },
            { EmployeeId: 191, ReportsTo: 1, Name: "Tammy Plant" },
            { EmployeeId: 192, ReportsTo: 1, Name: "Ralph Brogan" }]
    });
    
    var lastEmployeeId = testDS.getCacheData().getProperty("EmployeeId").max();
    var getNewEmployeeId = function () {
        return ++lastEmployeeId;
    };
    
    var artificialRoot = { EmployeeId: 1, Name: "Old Artificial Root Node" };
    var treeProperties = {
        modelType: "parent",
        nameProperty: "Name",
        idField: "EmployeeId",
        parentIdField: "ReportsTo"
    };
    
    isc.TreeGrid.create({
        ID: "testTreeGrid",
        autoDraw: false,
        width: 500,
        height: 400,
        showOpenIcons: false,
        showDropIcons: false
    });
    
    isc.DynamicForm.create({
        ID: "addCommonParentForm",
        autoDraw: false,
        width: 250,
        fields: [{
            name: "parentName",
            title: "Parent Name",
            type: "text",
            required: true,
            defaultValue: "New object from user"
        }]
    });
    
    isc.Button.create({
        ID: "addCommonParentButton",
        autoDraw: false,
        title: "Add parent",
        click : function () {
            var artificialRootId = artificialRoot.EmployeeId,
                newParentId = getNewEmployeeId();
    
            var newParent = {
                EmployeeId: newParentId,
                ReportsTo: artificialRootId,
                Name: addCommonParentForm.getValue("parentName")            
            };
    
            // Change the parent pointer of the root's children to the id of the new parent node.
            var data = testDS.getCacheData();
            for (var i = 0, len = data.length; i < len; ++i) {
                if (data[i].ReportsTo == artificialRootId) {
                    data[i].ReportsTo = newParentId;
                }
            }
    
            data.push(newParent);
    
            testTreeGrid.setData(isc.Tree.create({
                root: testDS.copyRecord(artificialRoot),
                data: data
            }, treeProperties));
            testTreeGrid.getData().openAll();
        }
    });
    
    isc.VLayout.create({
        width: 550,
        height: 500,
        margin: 10,
        membersMargin: 10,
        members: [testTreeGrid, addCommonParentForm, addCommonParentButton]
    });
    
    testDS.fetchData(null, function (dsResponse, data, dsRequest) {
        testTreeGrid.setData(isc.Tree.create({
            root: testDS.copyRecord(artificialRoot),
            data: data
        }, treeProperties));
        testTreeGrid.getData().openAll();
    });

    Comment

    Working...
    X