Search code examples
javascriptjqueryjqgridtreegrid

Add new rows to jqGrid Treegrid model


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"}
]}

Solution

  • 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.