4

I have JSON data and that JSON data has parent child relation . I Want to create tree structure from it. i found many plugins and libraries but i can't found my requirement . I am getting this JSON data using PHP script.

Here is image that has tree structure that i want to create . i'm stuck at it.I know JSON is not as displayed in image but i only want to show you what a tree should look like .How to create tree like in image.All i want is javascript code to handle and create this type of structure of tree . Working example is must & much appreciated.

You can use JSON format as you like and tree should be collapsible.Also provide required JSON format for it.

enter image description here

and my JSON data as follows :

     {
     "2":
         {
          "5": "Wrist Watch"
         },
     "5":
         {
          "9": "Men's"
         },
     "18": 
         {
         "3": "Clothing"
         },
     "28": 
         {
         "1": "Perfumes"
         },
     "29": 
         {
         "7": "Laptop",
         "10": "Tablets"
         },
     "30":
         {
          "8": "Mobile"
         },
    "31": 
         {
          "2": "Books"
         },
    "33": 
         {
          "6": "Electronics"
         },
   "34": 
         {
          "4": "Home & Kitchen\n"
         }
    }
Deep Shah
  • 1,334
  • 3
  • 20
  • 41
  • Your JSON doesn't define the ordering - it should use arrays instead of objects if the order is important. Also, the containers have no label defined with this structure. – Wladimir Palant Nov 11 '14 at 13:47
  • Hello, a quick question before I approach to answer this: do you neccessarily search for an Object? I could show you a way to set up a Directory class so you could add subdirectorys and such via method in a more object oriented manner – Max Bumaye Nov 17 '14 at 12:19
  • @MaxBumaye can you please show me your answer. – Deep Shah Nov 17 '14 at 17:26
  • http://stackoverflow.com/questions/26930142/how-to-create-tree-structure-recursive-json-query-for-it-where-id-5-in-nodej?noredirect=1#comment42406137_26930142 – Max Bumaye Nov 17 '14 at 17:46
  • look at my answer with the directory Class there - is it something like that you are looking for? – Max Bumaye Nov 17 '14 at 17:46
  • @Deep Shah: I had a mistake in my code (I used `branch` instead of `item` for the `.children` recursion `if` test, now fixed). – iCollect.it Ltd Nov 24 '14 at 10:15

6 Answers6

10

If you want to roll your own, the keyword in "trees" is recursion. It needs to support any depth of data and the code and data should both support recursion.

This means your JSON data should be a recursive structure, where each node looks the same (and looks something like this):

{
    id: 1,                  // data id
    title: "title",         // display title
    children: [             // list of children, each with this same structure
        // list of child nodes
    ]
}

Note: I have changed the sample data to contain more depth as 2 levels never shows up recursion problems.

e.g.:

{
    id: 0,
    title: "root - not displayed",
    children: [{
        id: 1,
        title: "Option 1",
        children: [{
            id: 11,
            title: "Option 11",
            children: [{
                id: 111,
                title: "Option 111"
            }, {
                id: 112,
                title: "Option 112"
            }]
        }, {
            id: 12,
            title: "Option 12"
        }]
    }, {
        id: 2,
        title: "Option 2",
        children: [{
            id: 21,
            title: "Option 21"
        }, {
            id: 22,
            title: "Option 22"
        }]
    }, {
        id: 3,
        title: "Option 3",
        children: [{
            id: 31,
            title: "Option 31"
        }, {
            id: 32,
            title: "Option 32"
        }]
    }]
}

The recursive function looks like this:

function addItem(parentUL, branch) {
    for (var key in branch.children) {
        var item = branch.children[key];
        $item = $('<li>', {
            id: "item" + item.id
        });
        $item.append($('<input>', {
            type: "checkbox",
            name: "item" + item.id
        }));
        $item.append($('<label>', {
            for: "item" + item.id,
            text: item.title
        }));
        parentUL.append($item);
        if (item.children) {
            var $ul = $('<ul>').appendTo($item);
            addItem($ul, item);
        }
    }
}

JSFiddle: http://jsfiddle.net/0s0p3716/188/

The code recurses the structure, adding new ULs and LIs (with checkbox etc ) as it goes. The top level call just provides the initial root starting points of both the display and the data.

addItem($('#root'), data);

The end result looks like this:

enter image description here

If you want to toggle visibility, based on the checked state, use this:

$(':checkbox').change(function () {
    $(this).closest('li').children('ul').slideToggle();
});

If you also want the labels to toggle the checkboxes, use this:

$('label').click(function(){
    $(this).closest('li').find(':checkbox').trigger('click');
});

Note: I have only provided the most basic of styling as that will typically be "to taste". Examples in links were shown in another answer.

-- updated:

amended: possible wrong ids for items 31 & 32?

function for better selection and deselection(for parents cascading into child nodes):

$(function () {
addItem($('#root'), data);
$(':checkbox').click(function () {
    $(this).find(':checkbox').trigger('click');
    var matchingId = $(this).attr('id');
    if ($(this).attr('checked'))
    {
        $('input[id*=' + matchingId +']').each(function() {
        $(this).removeAttr('checked');
            $(this).prop('checked', $(this).attr('checked'));
        });
    }
    else {
         $('input[id*=' + matchingId +']').each(function() {
        $(this).attr('checked', 'checked');
            $(this).prop('checked', $(this).attr('checked'));

         });
    }

});
$('label').click(function(){
    $(this).closest('li').children('ul').slideToggle();

});

-- Update the fiddle with this as shown here(JsFiddle) and it will work better and also will allow you to click the text to expand without selecting at the same time - I know I find this far more useful. It will help (and this is personal preference) you to see what values and options are available without having to select the first.

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • thanks for perfect answer another favor needed . I have JSON encoded data that are came from php code . var data = ''; when i do this this doesn't give me tree Where $str is json data and format as you specify.What's problem with my code? – Deep Shah Nov 22 '14 at 13:00
  • @Deep Shah: Check the request and response using Fiddler2, which will decode the response visually so you can validate it. If that does not help, create a new question (with full code) and add a link to it here :) – iCollect.it Ltd Nov 24 '14 at 10:09
  • there is small problem in your code . Child checkboxes doesn't get uncheck after closing parent node. can you please update your code? – Deep Shah Dec 06 '14 at 12:27
  • Noob question: What purpose does the `$item.append();` serve here? – davidtgq Aug 19 '17 at 04:14
  • 1
    @dtgq: The purpose was to see if anyone actually reads the code (just kidding). It was just left over from an earlier code edit before I changed to use `appendTo`. I have removed it and updated the Fiddle. Thanks :) – iCollect.it Ltd Aug 19 '17 at 18:47
3

The thing with programming is: existing libraries and tools rarely do exactly what you need. It's always up to you to convert the input data into exactly the format they expect and then the output data into the format you need. Occasionally this conversion requires more effort than writing your own code instead of a library function - this seems to be one of those occasions.

As @philosophocat already noted, the best way to present such a tree in HTML markup would be nested lists. All you need is iterate through the JSON data recursively and create the corresponding elements:

function createList(data)
{
  var result = document.createElement("ul");
  for (var key in data)
  {
    if (!data.hasOwnProperty(key) || key == "_title")
      continue;
    var value = data[key];
    var item = createItem(key, typeof value == "string" ? value : value._title);
    if (typeof value == "object")
      item.appendChild(createList(value));
    result.appendChild(item);
  }
  return result;
}

function createItem(value, title)
{
  var result = document.createElement("li");
  var checkbox = document.createElement("input");
  checkbox.setAttribute("type", "checkbox");
  checkbox.setAttribute("name", "selection");
  checkbox.setAttribute("value", value);
  result.appendChild(checkbox);
  result.appendChild(document.createTextNode(title));
  return result;
}

document.body.appendChild(createList(jsonData));

Note that the order in which the items appear is "random" here, as object keys are generally unordered. You can change the code above to sort the keys somehow, or you can change the data to use arrays and define an order. I also added a "_title" property to the data to make sure the categories are labeled - your data doesn't have any labels at all for the categories.

Now you need to style the lists in such a way that they look like a tree. The obvious solution is using the list-style-image CSS property to replace the usual bullet points by a grid lines image. However, that doesn't work for nested lists - there you need to show multiple images, vertical lines from the higher-level lists as well as the image actually belonging to the current list item.

This can be solved by using background images for the list items instead, these background images will be shown next to sublists as well then. Here are the example styles I've got:

ul
{
  padding: 0;
  list-style-type: none;
  font-size: 14px;
}

li
{
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAABkCAYAAABdELruAAAAP0lEQVR42u3PQQoAIAgEQP3/o6t7JAhdolkQD4sMZuwZazKKlGXniHRDOu6HfyKRSCQSiUQikUgkEolEIv0rTc/fNmQ78+lPAAAAAElFTkSuQmCC);
  background-repeat: no-repeat;
  padding-left: 13px;
}

li:last-child
{
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAHUlEQVR42mNkwAT/gZiRAQ/AK0mKplGbqGETThoACFgJCVdBEqAAAAAASUVORK5CYII=);
}

li > ul
{
  margin-left: 5px;
}

Note that this will still get ugly if the sublist is too high - the height of the background image I used is merely 100px. This can be solved by using a larger background image of course. A cleaner alternative would be using border-image-slice CSS property but that one is currently only supported in Firefox.

Fiddle for this code

Edit: This article goes into more detail on styling nested lists like a tree. While the approach is similar, it manages to avoid the image size issues I mentioned above by using a separate image for the vertical line which can be repeated vertically. On the downside, that approach looks like it might only work with solid lines and produce artifacts if applied to dotted lines.

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
1

Use http://www.jstree.com/. This library provides each function I ever need when working with trees and javascript.

You simple have to change your json-response according to the given format (http://www.jstree.com/docs/json/):

{
  id          : "string" // will be autogenerated if omitted
  text        : "string" // node text
  icon        : "string" // string for custom
  state       : {
    opened    : boolean  // is the node open
    disabled  : boolean  // is the node disabled
    selected  : boolean  // is the node selected
  },
  children    : []  // array of strings or objects
  li_attr     : {}  // attributes for the generated LI node
  a_attr      : {}  // attributes for the generated A node
}

Set up the javascript and include all required files and there you go. I just skip repeating the documentation by referring to it: http://www.jstree.com/

Qullbrune
  • 1,925
  • 2
  • 20
  • 20
0

I'm using DynaTree for an internal site at work and it works fantastic.

  1. Download DynaTree
  2. Format your JSON as such (taking your screenshot as an example):
{
  "title": "Sports & Outdoors",
  "isFolder": true,
  "key": "0",
  "children": [
        {
          "title": "Fitness Accessories",
          "key": "1",
          "isFolder": true,
          "children": [
            {
              "title": "Fitness Accessories",
              "key": "2",
              "isFolder": true,
              "children": [
                {
                  "title": "Pedometer & Watches",
                  "key": "3" 
                }
              ] 
            }
          ]
        }
      ]
}
  1. Run this JS on page load:
    $("#buildTree").dynatree({
      onActivate: function (node) {
        // A DynaTreeNode object is passed to the activation handler
        // Note: we also get this event, if persistence is on, and the page is reloaded.
        leftActiveNodeKey = node.data.key;
      },
      persist: false,
      checkbox: true,
      selectMode: 3,
      children: $.parseJSON(response.d)
    });

  1. To get the selected nodes you can use:

    var selectedNodes = $("#buildTree").dynatree("getTree").getSelectedNodes();

Dynatree is pretty customization, both in look and function. Read through the documentation for the settings you need.

sbonkosky
  • 2,537
  • 1
  • 22
  • 31
0

@Gone Coding's example is excellent, but the child check boxes will not show as 'uncheked' even though the checked attribute is removed, as rendered in Chrome. If you add,

$(this).prop('checked', false);

to the code, so it reads as

$(function () {
    addItem($('#root'), data);
    $(':checkbox').click(function () {
        var matchingId = $(this).attr('id');
        if ($(this).attr('checked'))
        {
            $('input[id*=' + matchingId +']').each(function() {
            $(this).removeAttr('checked');
            $(this).prop('checked', false);
                $(this).prop('checked', $(this).attr('checked'));
                return;
            });
        }
        else {
             $('input[id*=' + matchingId +']').each(function() {
            $(this).attr('checked', 'checked');
                $(this).prop('checked', $(this).attr('checked'));
             });
        }

    });
    $('label').click(function(){
        $(this).closest('li').children('ul').slideToggle();

    });
});

the child check boxes will fill or clear when the user makes a change.

CAtoOH
  • 71
  • 1
  • 7