Need help to format this data

@James_Hibbard

Hi I need some help please, I am using dynatree, but the problem, is that I cannot format the returned json data from my server. Is this possible to format the structure in client side ?

Here is the return json data.

      {
       "roles":[
          {
             "id":1,
             "title":"Produce",
             "level":"01",
             "parent":null
          },
          {
             "id":2,
             "title":"create",
             "level":"0101",
             "parent":"1"
          },
          {
             "id":3,
             "title":"edit",
             "level":"0102",
             "parent":"1"
          },
          {
             "id":4,
             "title":"delete",
             "level":"010203",
             "parent":"1"
          },
          {
             "id":5,
             "title":"Seafood",
             "level":"02",
             "parent":null
          },
          {
             "id":6,
             "title":"create",
             "level":"0201",
             "parent":"5"
          },
          {
             "id":7,
             "title":"edit",
             "level":"0202",
             "parent":"5"
          },
          {
             "id":8,
             "title":"delete",
             "level":"0203",
             "parent":"5"
          }
       ],
       "groups":[
          {
             "id":1,
             "role":"admin"
          },
          {
             "id":2,
             "role":"superadmin"
          },
          {
             "id":3,
             "role":"staff"
          },
          {
             "id":4,
             "role":"warehouse"
          }
       ]
    }

And I want to form it like this

 var treeData = [

        {title: "admin",
            children: [
                {title: "Produce",
                    children: [
                        {title: "create", key: "2" },
                        {title: "edit", key: "3" },
                        {title: "delete", key: "4" }
                    ]
                },
                {title: "SeaFood",
                    children: [
                        {title: "create", key: "6" },
                        {title: "edit", key: "7" },
                        {title: "delete", key: "8" }
                    ]
                }

            ]
        },
         {title: "superadmin",
            children: [
                {title: "Produce",
                    children: [
                        {title: "create", key: "2" },
                        {title: "edit", key: "3" },
                        {title: "delete", key: "4" }
                    ]
                },
                {title: "SeaFood",
                    children: [
                        {title: "create", key: "6" },
                        {title: "edit", key: "7" },
                        {title: "delete", key: "8" }
                    ]
                }

            ]
        },
         {title: "staff",
            children: [
                {title: "Produce",
                    children: [
                        {title: "create", key: "2" },
                        {title: "edit", key: "3" },
                        {title: "delete", key: "4" }
                    ]
                },
                {title: "SeaFood",
                    children: [
                        {title: "create", key: "6" },
                        {title: "edit", key: "7" },
                        {title: "delete", key: "8" }
                    ]
                }

            ]
        },
         {title: "warehouse",
            children: [
                {title: "Produce",
                    children: [
                        {title: "create", key: "2" },
                        {title: "edit", key: "3" },
                        {title: "delete", key: "4" }
                    ]
                },
                {title: "SeaFood",
                    children: [
                        {title: "create", key: "6" },
                        {title: "edit", key: "7" },
                        {title: "delete", key: "8" }
                    ]
                }

            ]
        }
    ];

Thank you in advance.

Two ways, very high level

The ā€œproperā€ way:

1. Loop through all the array elements under the groups array.
   1.1 For each element under groups:
       1.1.1 Create a new element, with the role as your title.
	   1.1.2 Loop through all the array elements under the roles array which have a null parent, adding new elements as children of this element
	         1.1.2.1 For each element under roles:
				1.1.2.1.1 Create a new element with title of the role.  This element will be a child of the element created in 1.1.1
				1.1.2.1.2 Repeat loop 1.1.2, except pass the id of THIS element, adding new elements as children of this element
					1.1.2.1.2.1 Repeat this loop until there are NO child array elements created.

The ā€œquick and dirtyā€ method

1 Loop through all the array elements under the roles array which have a null parent, adding new elements as children of this element
	1.1 For each element under roles:
		1.1.1 Create a new element with title of the role.  This element will be a child of the element created in 1.1.1
		1.1.2 Repeat loop 1, except pass the id of THIS element, adding new elements as children of this element
			1.1.2.1 Repeat this loop pattern until there are NO child array elements created.
2. Loop through all the array elements under the groups array.
   2.1 For each element under groups, add the array created in step 1 as the children.

The advantage of the ā€œproperā€ way is itā€™s easier to later tie the groups to the roles (i.e., you only want admins to have the rights to create).

The advantage to the ā€œquick and dirtyā€ method is it only does one loop through the roles array, then you can do a bulk addition for each group.

1 Like

Hi DaveMaxwell,

I just want to ask before I will continue the this, is this correct ?..looks like I am getting plenty of error in formatting.

var group = data.groups;
var role = data.roles;

var tree = [];
for(var i=0;i<group.length;i++){
       var x = '{title:'+group[i].role+','+
           'children:[' +
                for(var a=0;a<role.length;i++){
                   
                }
              +']'+
        '}'
}

Iā€™d assume itā€™s far easier to create the object than to create the object string.

what do you mean ? to create it in server side ?.. can you help me please how to do it in object ?

You canā€™t put a for loop inside an expression (also, you must not use the HTML-escaped representation of <). What you can do is call a function there, which returns the result of the loop. Like e.g.

var group = data.groups;
var role = data.roles;
var x;

function getRoles() {
  var roles = '';

  for(var a in role) {
    roles += '{title: ' + role[a].title + '},'; // etc.
  }

  return roles;
}

for (var i in group){
  x = '{title:' + group[i].role + ',' +
    'children:[' + getRoles() + ']}';
}

Are the roles to be different depending on the group? In this case you might pass the current group index to the function and process the data accordingly; otherwise you might just call it once and store the result in a variable.

PS: This will of course generate a JSON stringā€¦ or do you want to create that array of objects directly?

Out of curiousity, why canā€™t you format it on server side?

This method groupBy from underscore seems it would help a bit.

http://underscorejs.org/#groupBy

_.groupBy([ā€˜oneā€™, ā€˜twoā€™, ā€˜threeā€™], ā€˜lengthā€™);
=> {3: [ā€œoneā€, ā€œtwoā€], 5: [ā€œthreeā€]}

Thatā€™s some nice thinking there.

Iā€™ve put together some code that implements this technique.

var treeDataConverter = (function iife() {
    function addChildren(group, id, data) {
        function matchingId(roleInfo) {
            return Number(roleInfo.parent) === Number(id);
        }
        // 1.1.2.1 For each element under roles:
        var children = data.roles.filter(matchingId)
            .map(function nestedChildren(roleInfo) {
                // 1.1.2.1.1 Create a new element with title of the role.
                // This element will be a child of the element created in 1.1.1
                var role = {
                    title: roleInfo.title
                };
                if (id !== null) {
                    role.key = roleInfo.id;
                }
                // 1.1.2.1.2 Repeat loop 1.1.2, except pass the id of THIS element ...
                var roleChildren = addChildren(role, roleInfo.id, data);
                // ..., adding new elements as children of this element
                if (roleChildren.length > 0) {
                    role.children = roleChildren;
                }
                return role;
            });
        // 1.1.2.1.2.1 Repeat this loop until there are NO child array elements created.
        if (children.length === 0) {
            return [];
        }
        group.children = children;
        return group;
    }
    return function TreeDataConverter(data) {
        // 1. Loop through all the array elements under the groups array.
        return data.groups.map(function getGroupChildren(groupInfo) {
            // 1.1 For each element under groups:
            // 1.1.1 Create a new element, with the role as your title.
            var groupData = {
                title: groupInfo.role
            };
            // 1.1.2 Loop through all the array elements under the roles array
            // which have a null parent, adding new elements as children of this element
            var group = addChildren(groupData, null, data);
            return group;
        });
    };
}());

Some of the nesting Iā€™d like to unpack, such as how the addChildren function calls nestedChildren, which in turn calls addChildren. Thinking my way out of that quandary gets challenging though.

A working example can be explored at https://jsfiddle.net/pmw57/7h9c0f1s/1/

2 Likes

@Paul_Wilkins,

Thank you it helps me a lot, How can I check those nodes if the id of roles exist in allowrole(access) with the status of 1. ?

{
   "roles":[
      {
         "id":1,
         "title":"Produce",
         "level":"01",
         "parent":null
      },
      {
         "id":2,
         "title":"create",
         "level":"0101",
         "parent":"1"
      },
      {
         "id":3,
         "title":"edit",
         "level":"0102",
         "parent":"1"
      },
      {
         "id":4,
         "title":"delete",
         "level":"010203",
         "parent":"1"
      },
      {
         "id":5,
         "title":"Seafood",
         "level":"02",
         "parent":null
      },
      {
         "id":6,
         "title":"create",
         "level":"0201",
         "parent":"5"
      },
      {
         "id":7,
         "title":"edit",
         "level":"0202",
         "parent":"5"
      },
      {
         "id":8,
         "title":"delete",
         "level":"0203",
         "parent":"5"
      }
   ],
   "groups":[
      {
         "id":1,
         "role":"admin"
      }
   ],
  "allowrole": [{
        "group": "1",
        "access": "1",
        "status": "1"
    }, {
        "group": "1",
        "access": "2",
        "status": "1"
    }, {
        "group": "1",
        "access": "3",
        "status": "1"
    }, {
        "group": "1",
        "access": "4",
        "status": "1"
    }, {
        "group": "1",
        "access": "5",
        "status": "0"
    }, {
        "group": "1",
        "access": "6",
        "status": "1"
    }, {
        "group": "1",
        "access": "7",
        "status": "1"
    }, {
        "group": "1",
        "access": "8",
        "status": "1"
    }] 
   
}

Thank you in advance.

I take it that the access part changes the treeData structure? Please show us how.

Hi Paul_Wilkins,

Here is the example that will check or selected the chechbox when the page load see tree2 . dynatree. I want check the roles or selected. if the roles id is the same in allowrole access . the allowrole will not create checbox this is only the reference of roles so that the checkbox of of roles will be selected if status is 1.

your solution works and display correctly as what I needed but I want also the checkbox to be selected or checked.

--Admin
|
|
| --produce
|     |
|       --create
|      |
|      --edit
|      |
|       --delete
|--seafood
     |
      --create
     |
      --edit
     |
      -delete

--Superadmin
|
|
| --produce
|     |
|       --create
|      |
|      --edit
|      |
|       --delete
|--seafood
     |
      --create
     |
      --edit
     |
      -delete
      
      
      
--Warehouse
|
|
| --produce
|     |
|       --create
|      |
|      --edit
|      |
|       --delete
|--seafood
     |
      --create
     |
      --edit
     |
      -delete
      
      
--Staff
|
|
| --produce
|     |
|       --create
|      |
|      --edit
|      |
|       --delete
|--seafood
     |
      --create
     |
      --edit
     |
      -delete

Thank you in advance.

Do you want to also incorporate the allowrole section in to the new data structure?

Hi Paul_Wilkins,

The allowrole is included in the response from my server, .Incorporate to new datatastructure ? you mean to have checkbox with allowrole ? ,No need to create checkbox for allowrole this is only the bases if the satus of access is 1 then the value of access is 3 and the role has id of 3 (edit under produce) then this role will be check. .etc.

something like this. I make small example

Thank you.

[quote=ā€œjemz, post:14, topic:230706, full:trueā€]
The allowrole is included in the response from my server, .Incorporate to new datatastructure ?[/quote]

Iā€™ve simplified a lot of the code (simple is robust, complex is fragile), and it now has a range of functions to help make the code easier to work with:

function getGroupId(name) {
    return opts.sourceData.groups.find(function (groupInfo) {
        return groupInfo.role === name;
    }).id;
}
function isInGroup(groupId) {
    return function (accessInfo) {
        return Number(accessInfo.group) === Number(groupId);
    };
}
function hasRole(roleId) {
    return function (accessInfo) {
        return Number(accessInfo.access) === Number(roleId);
    };
}
function getAccess(accessList, roleId) {
    var groupId = getGroupId(opts.groupTitle);
    var accessInfo = accessList.filter(isInGroup(groupId))
        .find(hasRole(roleId));
    return Number(accessInfo && accessInfo.status);
}
function hasMatchingId(id) {
    return function (roleInfo) {
        return Number(roleInfo.parent) === Number(id);
    };
}

I ended up relying on the Number() method as some of the values being compared were a combination of numbers and strings, for example:

        "id": 3,
...
        "access": "3",

The upshot is that the main engine of the code is now as simple as this:

var role = {
    title: roleInfo.title,
    key: roleInfo.id,
    access: getAccess(opts.sourceData.allowrole, roleInfo.id) || 0
};

The code for the converter is:

var treeDataConverter = (function iife() {
    function addChildren(container, opts) {
        function getGroupId(name) {
            return opts.sourceData.groups.find(function (groupInfo) {
                return groupInfo.role === name;
            }).id;
        }
        function isInGroup(groupId) {
            return function (accessInfo) {
                return Number(accessInfo.group) === Number(groupId);
            };
        }
        function hasRole(roleId) {
            return function (accessInfo) {
                return Number(accessInfo.access) === Number(roleId);
            };
        }
        function getAccess(accessList, roleId) {
            var groupId = getGroupId(opts.groupTitle);
            var accessInfo = accessList.filter(isInGroup(groupId))
                .find(hasRole(roleId));
            return Number(accessInfo && accessInfo.status);
        }
        function hasMatchingId(id) {
            return function (roleInfo) {
                return Number(roleInfo.parent) === Number(id);
            };
        }

        // 1.1.2.1 For each element under roles:
        var children = opts.sourceData.roles.filter(hasMatchingId(opts.id))
            .map(function nestedChildren(roleInfo) {
                // 1.1.2.1.1 Create a new element with title of the role.
                // This element will be a child of the element created in 1.1.1
                var role = {
                    title: roleInfo.title,
                    key: roleInfo.id,
                    access: getAccess(opts.sourceData.allowrole, roleInfo.id) || 0
                };
                // 1.1.2.1.2 Repeat loop 1.1.2, except pass the id of THIS element ...
                opts.id = roleInfo.id;
                // ..., adding new elements as children of this element
                return addChildren(role, opts) || role;
            });
        // 1.1.2.1.2.1 Repeat this loop until there are NO child array elements created.
        if (!children.length) {
            return;
        }
        return Object.assign(container, {children: children});
    }
    return function TreeDataConverter(data) {
        // 1. Loop through all the array elements under the groups array.
        return data.groups.map(function getGroupChildren(groupInfo) {
            // 1.1 For each element under groups:
            // 1.1.1 Create a new element, with the role as your title.
            var groupData = {
                title: groupInfo.role
            };
            // 1.1.2 Loop through all the array elements under the roles array
            // which have a null parent, adding new elements as children of this element
            return addChildren(groupData, {
                sourceData: data,
                groupTitle: groupData.title,
                id: null
            });
        });
    };
}());

function convertHandler() {
    var jsondata = JSON.parse(document.querySelector("#jsondata").value);
    var treeData = treeDataConverter(jsondata);
    document.querySelector("#jsondata").value = JSON.stringify(treeData, undefined, 2);
}
document.querySelector("#convert").addEventListener("click", convertHandler, false);

and can be explored at https://jsfiddle.net/pmw57/7h9c0f1s/5/

Hi Paul_Wilkins,

Thank you again it display properly, I just notice when I tried to run the data in your example after the page load. the check box will not automatically be checked even it has status of 1.

Thank you.

[quote=ā€œjemz, post:17, topic:230706, full:trueā€]
Thank you again it display properly, I just notice when I tried to run the data in your example after the page load. the check box will not automatically be checked even it has status of 1.[/quote]

Currently it is not known by me how the checkboxes integrate with the tree data. It should take just a few tweaks to get things meshing together.

For example, from what Iā€™m seeing on the dynatree samples page, the syntax is:

{title: "item2: selected on init", select: true },

This should mean that instead of:

{
    ...,
    access: 1
}

that the following is more likely to work:

{
    ...,
    select: true
}

Which can be done with just one minor change. Rename access to select and test if the value equals 1:

//access: getAccess(opts.sourceData.allowrole, roleInfo.id) || 0
select: getAccess(opts.sourceData.allowrole, roleInfo.id) === 1

If you have a test page, further troubleshooting can take place from there.

Hi Paul_Wilkins,

I tried the your fiddle, https://jsfiddle.net/pmw57/7h9c0f1s/5/
and change this code from
access: getAccess(opts.sourceData.allowrole, roleInfo.id) || 0

to
select: getAccess(opts.sourceData.allowrole, roleInfo.id) === 1

when I click the convert button. the data structure having access:1 not select:true.

what I am missing ?

Best Regards

Did you press the run button so that the changes take effect.

1 Like

HI Paul_Wilkins,

Thank you so much for helping me. All is well now.

Best Regards.