1

We've created a jqGrid TreeGrid that represents a filesystem, where branches are folders and leafs are files. We've implemented functionality within the TreeGrid to create new "files" by using addChildNode which works well enough. However, we also want to add functionality to create new folders. Our script works which creates new folders, but they are not immediately displayed on the TreeGrid unless it or the page is reloaded. However, reloading the TreeGrid will collapse all the folders, which is particularly annoying.

Is there a way to selectively refresh the nodes of the TreeGrid, or to add a new branch in that is functional? I've seen some partial documentation on addJSONData, but using this function completely purges the TreeGrid until refresh. I've also attempted to use addChildNode and change certain properties, and I've tried to add in the row manually using DOM manipulation; however, both of these methods break the node that was inserted.

Edit:

var grid = $("#grid");
grid.jqGrid({
    treeGrid: true,
    treeGridModel: "adjacency",
    ExpandColumn: 'name',
    ExpandColClick: true,
    url:"",
    datatype:"json",
    colNames:['id','Name','Authorization','Views','Uri'],
    colModel:[ {name:'id', index:'id', hidden:true, key:true},
               {name:'name', index:'name', sorttype:"text", width:3, sortable:false},
               {name:'auth',index:'auth', sorttype:"text", sortable:false, hidden:true},
               {name:'views',index:'views', sorttype:"integer", width:1, sortable:false, align:"center"},
               {name:'uri',index:'uri',sorttype:'text',sortable:false,hidden:true}],
    jsonReader:{ root:"rows"
                ,page:"page"
                ,total:"total"
                ,records:"records"
                ,repeatitems:false
                ,cell:""
                ,id:"0"
                ,userdata:""
               },
    multiselect:false,
    autowidth:true,
    height:"auto",
    sortable:false,
    toppager:true,
    hidegrid: false,
    loadui: 'block',
    pager:"#grid_pager",
    caption: "Files",
});

A returned JSON request for the new folder looks something like this:

ret = {"error":"","total":1,"page":1,"records":1,"rows":[{"id":"1113","name":"test","uri":"accounting\/test\/","parent":1,"isLeaf":false,"expanded":true,"loaded":true}]}

Which I attempt to add in using:

grid[0].addJSONData(ret);

The initial data that is loaded is sent as JSON:

{"rows":[
    {"id":"1","uri":"afolder\/","parent_id":"0","name":"afolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"4","uri":"bfolder\/","parent_id":"0","name":"bfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"7","uri":"cfolder\/","parent_id":"0","name":"cfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"20","uri":"dfolder\/","parent_id":"0","name":"dfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"48","uri":"efolder\/","parent_id":"0","name":"efolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"179","uri":"ffolder\/","parent_id":"0","name":"ffolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"182","uri":"gfolder\/","parent_id":"0","name":"gfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"186","uri":"hfolder\/","parent_id":"0","name":"hfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"201","uri":"ifolder\/","parent_id":"0","name":"ifolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"239","uri":"jfolder\/","parent_id":"0","name":"jfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"253","uri":"kfolder\/","parent_id":"0","name":"kfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"262","uri":"lfolder\/","parent_id":"0","name":"lfolder","level":0,"parent":"0","isLeaf":"false"},
    {"id":"274","uri":"mfolder\/","parent_id":"0","name":"mfolder","level":0,"parent":"0","isLeaf":"false"}
]}
shmeeps
  • 7,725
  • 2
  • 27
  • 35
  • Adding code is always very helpful, because it can get many additional important information. Could you describe more about the TreeGrid which you use. Do you use local data or load the data from the server. Do you load whole grid at once or you load child nodes on expanding its parent node? Which grid model you has: Nested Set Model or Adjacency Model. How you get information about the new Folder which should be added in the treegrid? Will be filled the information from some page input controls or if will be loaded from the server? – Oleg Sep 07 '11 at 18:23
  • We use the adjacency model, and get the data in pieces as JSON requests from a MySQL Database as nodes are expanded. Information about the new folder is returned in a JSON request from creating the new folder record in the database via AJAX request. Added code. – shmeeps Sep 07 '11 at 18:34
  • Sorry, but I still not understand the situation. First you fill the tree grid with the data from the server. If the user click on the tree node the children of the node will be loaded from the server. How it come that one more node (folder) need be added? Why the data for the new folder contain a parent (`"parent":1`)? Probably the understanding how you fill the grid is connected to your main question. You wrote about getting "get the data in pieces", but from the database. I can't imagine such situation. If you make SELECT from the database you can send all results to the grid. Is it so? – Oleg Sep 07 '11 at 18:55
  • We haven't been able to get all the data at once, but possibly we just didn't implement it correctly. The reason to create a new node is because each node represents a folder, which a user can create new ones of. If we create a new folder, it creates the entry in the database, but does not update the grid, so we create a new node to show that the folder was actually created. Another acceptable solution would be to refresh the newly created folder's parent node. – shmeeps Sep 07 '11 at 19:04
  • Yes, we initially display the grid as just the root nodes. On expanding a node, the children from that node are retrieved and sent to the grid. We weren't able to implement a grid that obtained all of the data at one time. – shmeeps Sep 07 '11 at 19:06
  • I still don't understand who is the *initiator* of the creation of new node: the user or some process on the backend. It's also unclear why the JSON data contain `loaded":true`. The folder will stay empty? I am sure that your problem exist because you use the treegrid in the wrong way. Nevertheless I could provide you an example how to use `addChildNode` to add the node, but I need to have the test data for the treegrid. Then I could create a demo which add one note (your `ret` data) to the tree. – Oleg Sep 07 '11 at 20:09
  • @Oleg The user initiates the new node. Basically, they press a custom button in the grid which sends a folder name and parent id to the back end, which creates the folder and returns the JSON encoded string that has the added data (we do this in case there is an error, we can return the error instead of the row). The `loaded: true` was from something I was reading online that suggested using that. The folder would be empty unless the user creates a new folder or file (leaf) in it using the custom buttons in the grid. What test data do you need, just some JSON encoded data from one request? – shmeeps Sep 08 '11 at 22:32
  • Could you post just some data for the initial filling of tree grid. Then the new `ret` row could be added to the data. – Oleg Sep 08 '11 at 22:45
  • @Oleg I've posted the initial data as is, though I had to filter the names of the nodes. These are all branch/folder nodes. – shmeeps Sep 08 '11 at 23:12

2 Answers2

2

The demo shows how to use addChildNode method to add tree node. I added in the JSON data which you posted the "loaded":true part because I use in the test no server components and I want to load the treegrid at once.

To show that you should be very careful with ids of the new added row I added two buttons in the demo: "Insert tree node" and "Insert tree node with unique rowid". The first button use the id="1113" from the data which you posted. One click on the button work correct. the second click will insert rows having id duplicates which will be an error. The error you can see different in different web browsers. The second button use $.jgrid.randId() to generate unique rowid. It is probably not an option in your scenario, but it works perfect in case of local tree grid (like in my demo).

Another problem is that you use "parent":"0" in your demo for the root elements. Correct will be "parent":null or "parent":"null" (see the answer). Moreover the property with the name "parent_id" will be ignored. I removed from the demo some settings, so that local sorting can be used in the treegrid.

Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
1

We solved this problem by extending the functionality of the jqGrid source. First we created a function that could delete all of the child nodes of a particular folder (both folders/branches and files/leaves), so that we could reload them and hence get the latest set of children. This function takes an integer rowid just like delTreeNode().

delChildren : function (rowid) {
    return this.each(function () {
        var $t = this, rid = $t.p.localReader.id,
        left = $t.p.treeReader.left_field,
        right = $t.p.treeReader.right_field, myright, width, res, key;
        if(!$t.grid || !$t.p.treeGrid) {return;}
        var rc = $t.p._index[rowid];
        if (rc !== undefined) {
            // nested
            myright = parseInt($t.p.data[rc][right],10);
            width = myright -  parseInt($t.p.data[rc][left],10) + 1;
            var dr = $($t).jqGrid("getFullTreeNode",$t.p.data[rc]);
            if(dr.length>0){
                for (var i=0;i<dr.length;i++){
                    if(dr[i][rid] != rowid)
                        $($t).jqGrid("delRowData",dr[i][rid]);
                }
            }
            if( $t.p.treeGridModel === "nested") {
                // ToDo - update grid data
                res = $.jgrid.from($t.p.data)
                    .greater(left,myright,{stype:'integer'})
                    .select();
                if(res.length) {
                    for( key in res) {
                        res[key][left] = parseInt(res[key][left],10) - width ;
        }
                }
                res = $.jgrid.from($t.p.data)
                    .greater(right,myright,{stype:'integer'})
                    .select();
                if(res.length) {
                    for( key in res) {
                        res[key][right] = parseInt(res[key][right],10) - width ;
                    }
                }
            }
        }
    });
},

Then, we created a function to force reloading of a certain node (folder).

reloadNode: function(rc) {
        return this.each(function(){
            if(!this.grid || !this.p.treeGrid) {return;}

            var rid = this.p.localReader.id;

            $(this).jqGrid("delChildren", rc[rid]);

            var expanded = this.p.treeReader.expanded_field,
            parent = this.p.treeReader.parent_id_field,
            loaded = this.p.treeReader.loaded,
            level = this.p.treeReader.level_field,
            lft = this.p.treeReader.left_field,
            rgt = this.p.treeReader.right_field;

            var id = $.jgrid.getAccessor(rc,this.p.localReader.id);
            var rc1 = $("#"+id,this.grid.bDiv)[0];

            rc[expanded] = true;
            $("div.treeclick",rc1).removeClass(this.p.treeIcons.plus+" tree-plus").addClass(this.p.treeIcons.minus+" tree-minus");
            this.p.treeANode = rc1.rowIndex;
            this.p.datatype = this.p.treedatatype;
            if(this.p.treeGridModel == 'nested') {
                $(this).jqGrid("setGridParam",{postData:{nodeid:id,n_left:rc[lft],n_right:rc[rgt],n_level:rc[level]}});
            } else {
                $(this).jqGrid("setGridParam",{postData:{nodeid:id,parentid:rc[parent],n_level:rc[level]}} );
            }
            $(this).trigger("reloadGrid");
            rc[loaded] = true;
            if(this.p.treeGridModel == 'nested') {
                $(this).jqGrid("setGridParam",{postData:{nodeid:'',n_left:'',n_right:'',n_level:''}});
            } else {
                $(this).jqGrid("setGridParam",{postData:{nodeid:'',parentid:'',n_level:''}});
            }
        });
    },

This is the same as expandNode() save for that it does not check if the node was expanded to begin with, and forces it to send an AJAX request for the child elements of that node. This way, we always have the latest children.

Finally, we fixed a small bug in getRowData(), which prevented us from using it to supply the record argument to either expandNode() or our newly created reloadNode(). The issue was that the _id_ field in the JSON return was never created or filled. Adding that in fixed both expandNode() and reloadNode() The following is the changed source. Not ideal, but it works.

getRowData : function( rowid ) {
    var res = {}, resall, getall=false, len, j=0;
    this.each(function(){
        var $t = this,nm,ind;
        if(typeof(rowid) == 'undefined') {
            getall = true;
            resall = [];
            len = $t.rows.length;
        } else {
            ind = $t.rows.namedItem(rowid);
            if(!ind) { return res; }
            len = 2;
        }
        while(j<len){
            if(getall) { ind = $t.rows[j]; }
            if( $(ind).hasClass('jqgrow') ) {
                $('td',ind).each( function(i) {
                    nm = $t.p.colModel[i].name;
                    if ( nm !== 'cb' && nm !== 'subgrid' && nm !== 'rn') {
                        if($t.p.treeGrid===true && nm == $t.p.ExpandColumn) {
                            res[nm] = $.jgrid.htmlDecode($("span:first",this).html());
                        } else {
                            if($t.p.colModel[i].key != undefined && $t.p.colModel[i].key == true)
                            {
                                try {
                                    res["_" + nm + "_"] = $.unformat(this,{rowId:ind.id, colModel:$t.p.colModel[i]},i);
                                } catch (e){
                                    res["_" + nm + "_"] = $.jgrid.htmlDecode($(this).html());
                                }
                            }
                            try {
                                res[nm] = $.unformat(this,{rowId:ind.id, colModel:$t.p.colModel[i]},i);
                            } catch (e){
                                res[nm] = $.jgrid.htmlDecode($(this).html());
                            }
                        }
                    }
                });
                if(getall) { resall.push(res); res={}; }
            }
            j++;
        }
    });
    return resall ? resall: res;
},

Finally, we pull this all together as follows, with a JSON return object from creating a folder such as

{{"id":"1267", "name":"test15", "uri":"sample1\/test15\/", "parent_id":1, "parent":1, "isLeaf":false}

We call the function like

var parentid = ret.rows[0].parent;

var parent = grid.jqGrid('getRowData', parentid);

grid.jqGrid('reloadNode', parent);

The functions will delete all the child nodes of parent, then send an AJAX request for the new, updated list from the database. I'm going to push this onto the jqGrid Github if possible, since a reload function maybe useful for many people. I've posted it in here in case it isn't approved.

shmeeps
  • 7,725
  • 2
  • 27
  • 35
  • It sounds interesting, but before you use upvoting it would be polite to write an comment to my answer. Moreover you describe in your answer about *deleting* of nodes (`delTreeNode`), but your question was about the *adding* of nodes. Probably it was some problem which description you skip and which you are solving with respect of `delTreeNode`. In general if one need *add new node* the deleting of existing child nodes is wrong. – Oleg Sep 14 '11 at 16:32