Sunday, July 14, 2013

Custom Treeview using JavaScript- SharePoint 2010

we can find a lots of options for creating treeview. one option may be to use .NET treeview control itself. another option may be to write your own treeview using C#, Jquery etc. or get a JQuery treeview from google.
I looked for all the options. The .Net treeview has post back problem with each expand collapse.
The JQery treeview which I found on JQuery site was good but it has very large JQuery written for it and was more complicated to customize it. Also, with two level expand-collapse functionality with dynamically populated data made it very slow. To overcome from this problem, I decided to write my own TreeView for our own specific requirement.

We have a set of countries and for each countries we have set of categories and for each category there were a set of  subcategories. There were FAQs corresponding to each question. We had to show the countries, categories and subcategories in the form of treeview. I implemented it using javascript. The data I fetched using client object model from SharePoint list.

Below is the code-

<script type="text/javascript">

    ExecuteOrDelayUntilScriptLoaded(getData, "sp.js");   //this is necessary to ensure the library is loaded before function triggered
    var namedListItem;
    var countryItems;
    var CatItems;
    var subcatItems;
    var country;
    var category;
    var subCategory;
    var clientContext;
    function getData() {


        clientContext = SP.ClientContext.get_current();
         var region = clientContext.get_web().get_lists().getByTitle('Region'); 
        country = clientContext.get_web().get_lists().getByTitle('Country');
        category = clientContext.get_web().get_lists().getByTitle('Category');
        subCategory = clientContext.get_web().get_lists().getByTitle('SubCategory');
        var camlQuery = new SP.CamlQuery();
        camlQuery.set_viewXml(''); //caml statement goes here between the single quotes
        namedListItem = region.getItems(camlQuery);
        clientContext.load(namedListItem, 'Include(ID, Region)');
        clientContext.executeQueryAsync(Function.createDelegate(this, this.onRegionQuerySucceeded), Function.createDelegate(this, this.onRegionQueryFailed));
        
    }

    function onRegionQuerySucceeded(sender, args) {
        var listItemInfo = '';
        var listItemEnumerator = namedListItem.getEnumerator();
        while (listItemEnumerator.moveNext()) {
            var oListItem = listItemEnumerator.get_current();
            document.forms[0]["ddlRegion"].options.add(new Option(oListItem.get_item('Region'), oListItem.get_item('Region')));
           
        }
        var countryCaml = new SP.CamlQuery();
        var selectedRegion = document.getElementById('ddlRegion');
        if (selectedRegion.selectedIndex != -1) {
            countryCaml.set_viewXml('' + selectedRegion.options[selectedRegion.selectedIndex].value + '
');
        }
        else {
            countryCaml.set_viewXml('5');
        }

        var catCaml = new SP.CamlQuery();
        catCaml.set_viewXml('5');

        var subcatCaml = new SP.CamlQuery();
        subcatCaml.set_viewXml('5');


        countryItems = country.getItems(countryCaml);
        CatItems = category.getItems(catCaml);
        subcatItems = subCategory.getItems(subcatCaml);

        clientContext.load(countryItems, 'Include(ID, Country, RegionLookup)');
        clientContext.load(CatItems, 'Include(ID, Category)');
        clientContext.load(subcatItems, 'Include(ID, SubCategory, CategoryLookup)');

        clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
    }

    function onQuerySucceeded(sender, args) {

        var countryEnumerator = countryItems.getEnumerator();
        var catEnumerator = CatItems.getEnumerator();
        var subCatEnumerator = subcatItems.getEnumerator();
        mainDiv.innerHTML = '';
        var dv = document.createElement('div');
        dv.innerHTML = '';
        var i = 1;
        var j = 1;
        var k = 1;
        while (countryEnumerator.moveNext()) {
            var countries = countryEnumerator.get_current();
            mainDiv.appendChild(dv);
//below inner HTML will be generated dynamically on which treeview script will work.
//generated HTML should strictly be in the same format for treeview to work

            dv.innerHTML += '
 + i +
'"& gt;' + '+i+'"& gt;' + countries.get_item('Country') + '
+i+
'" style="display:none"& gt;
';
            while (catEnumerator.moveNext()) {
                var categories = catEnumerator.get_current();
                document.getElementById('categoryContainer' + i).innerHTML += '
 + i + j +
'" style="padding-left:10px"& gt;' + ' + i + j + '"& gt;' + categories.get_item('Category') + '
 + i + j +
'" style="display:none"& gt;';
                //method ExpandCollapse() will be responsible for expand collapse to work               while (subCatEnumerator.moveNext()) {
                    var subcategories = subCatEnumerator.get_current();
                    document.getElementById('subCategoryContainer' + i + j).innerHTML += '
 + i + j + k +
'" style="padding-left:10px" & gt;' + '::' + subcategories.get_item('SubCategory') + '
';
                    k++;
                }
                subCatEnumerator.reset();
                j++;
            }
            catEnumerator.reset();
            i++;
        }//treeview HTML ends
    }

    function onQueryFailed(sender, args) {

        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
    function onRegionQueryFailed(sender, args) {

        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
//countries will be retrieved based on region selected from dropdown.
    function getCountryCategory(ddl) {
        clientContext = SP.ClientContext.get_current();
        country = clientContext.get_web().get_lists().getByTitle('Country');
        countryCaml = new SP.CamlQuery();
        if (ddl.selectedIndex != -1) {
            countryCaml.set_viewXml('' + ddl.options[ddl.selectedIndex].value + '');
        }
        else {
            countryCaml.set_viewXml('');
        }
        countryItems = country.getItems(countryCaml);
        clientContext.load(countryItems, 'Include(ID, Country, RegionLookup)');
        clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
    }

    //treeview javascript starts here
   
    function ExpandCollapse(obj) {
            var catContainer = obj.nextSibling.nextSibling;
            var imgArr = catContainer.getElementsByTagName('img');
            if (imgArr.length > 0) {
                for (var c = 0; c < imgArr.length; c++) {
                    if (imgArr[c].src.indexOf('minus.gif') != -1) {
                        setCountryCategoryVisibility(imgArr[c]);
                    }
                }
            }
            setCountryCategoryVisibility(obj);
    }

    function setCountryCategoryVisibility(obj) {
        if (obj.src.indexOf('minus.gif') != -1) {
            obj.src = "/_layouts/Grail/Images/plus.gif";
            var subCatContainer = obj.nextSibling.nextSibling;
            subCatContainer.style.display = 'none';
        }
        else {
            obj.src = "/_layouts/Grail/Images/minus.gif";
            var subCatContainer = obj.nextSibling.nextSibling;
            subCatContainer.style.display = 'inline';
            toggleNodes(obj);//toggle other nodes
        }
    }

    function toggleNodes(obj) {
    var currentImage;
    var divArr = mainDiv.getElementsByTagName('div');
    if (divArr.length > 0) {
        for (var c = 0; c < divArr.length; c++) {
            if (divArr[c].id != '') {
                var currentDiv = document.getElementById(divArr[c].id);
                var currentImgArr = currentDiv.getElementsByTagName('img');
                for (var currImgCount = 0; currImgCount < currentImgArr.length; currImgCount++) {
                    if (currentImgArr[currImgCount].id == obj.id) {
                        currentImage = 'nottobecollapsed';
                        break;
                    }
                    else {
                        currentImage = 'tobecollapsed';
                    }
                }
                if (currentImage != 'nottobecollapsed') {
                    var divToBeToggled = document.getElementById(divArr[c].id);
                    var otherImages = divToBeToggled.getElementsByTagName('img');
                    for (var imagecount = 0; imagecount < otherImages.length; imagecount++) {
                        otherImages[imagecount].src = "/_layouts/Grail/Images/plus.gif";
                        var catSubCatContainer = otherImages[imagecount].nextSibling.nextSibling;
                        catSubCatContainer.style.display = 'none';
                    }
                }
            }
        }
    }//    //treeview javascript ends here
    }
</script>
<asp:Panel ID="clientTreeviewContainer" runat="server" >
<asp:Label ID="lblRegions" runat="server" Text="Select your Region" ></asp:Label><br /><br />

<select id="ddlRegion" onchange="javascript:getCountryCategory(this);"></select>
<br /><hr class="Grail-hrLine" />
<div id="mainDiv" >

</div>
</asp:Panel>

That's it.
This implementation requires very small javascript and will be applicable for n level expand-collapse in the treeview but the condition is- the generated HTML should strictly be in the same format as generated here.