Arip H
Arip H

Reputation: 67

Indented organization tree

I'm looking for some algorithm solution for building organization tree / graph which allow indented or offsetted one or more level.

I have have set of json data:

[{
   "id": 1,
   "level":1,
   "parent_id": 0,
   "name": "Board of Director"
}, {
   "id": 2,
   "level":2,
   "parent_id": 1,
   "name": "Finance Division"
}, {
   "id": 3,
   "level":3,
   "parent_id": 2,
   "name": "Finance Department"
}, {
   "id": 4,
   "level":3,
   "parent_id": 2,
   "name": "Sales Department"
}, {
   "id": 5,
   "level":3,
   "parent_id": 1,
   "name": "HR Division"
}]

Brief explanation: _parent_id_ attribute to connect divisions with its parent by using line, while level attribute for classifying where division should located or indented, for example : Level 1 (Board of Director), level 2 (Finance Division) and level 3 (Finance Department, Sales Department, HR Division). Due to HR Division has _parent_id_=1 and level=3, then position in chart should be on line level with Finance Department and Sales Department.

Below is mockup organization tree and examples of Indented as HR Division shown.

.tree li {
  list-style-type: none;
  margin: 0;
  padding: 10px 5px 0 5px;
  position: relative
}
.tree li::before,
.tree li::after {
  content: '';
  left: -20px;
  position: absolute;
  right: auto
}
.tree li::before {
  border-left: 1px solid #999;
  bottom: 50px;
  height: 100%;
  top: 0;
  width: 1px
}
.tree li::after {
  border-top: 1px solid #999;
  top: 30px;
  width: 25px
}
.tree li div {
  border: 1px solid #999;
  display: inline-block;
  padding: 8px 24px;
  text-decoration: none
}
.tree li.parent > div {
  cursor: pointer
}
.tree > ul > li::before,
.tree > ul > li::after {
  border: 0
}
.tree li:last-child::before {
  height: 30px
}
.tree li.parent > div:hover,
.tree li.parent > div:hover + ul li div {
  background: #f3f3f4;
  border: 1px solid #94a0b4;
  color: #000
}
li div.just-line {
  display: none;
}
div.just-line + ul > li::before {
  padding: 0;
  border-left: 0;
}
div.just-line + ul > li::after {
  left: -40px;
  width: 45px;
}
.no-padding {
  padding: 0 !important;
}
<div class="tree">
  <ul>
    <li data-id="1">
      <div>Board of Director</div>
      <ul>
        <li>
          <div>Finance Division</div>
          <ul>
            <li>
              <div>Finance Department</div>
              <li>
                <div>Sales Department</div>
              </li>
          </ul>
          </li>
          <li class="no-padding">
            <div class="just-line"></div>
            <ul>
              <li>
                <div>HR Division</div>
              </li>
            </ul>
          </li>
      </ul>
      </li>
  </ul>

Any help or suggestion would be much appreciated. Thanks.

Upvotes: 2

Views: 1270

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386654

Just to get you started, this is a two step solution which generates first a tree and the renders the DOM.

var data = [{ "id": 1, "level": 1, "parent_id": 0, "name": "Board of Director" }, { "id": 2, "level": 2, "parent_id": 1, "name": "Finance Division" }, { "id": 3, "level": 3, "parent_id": 2, "name": "Finance Department" }, { "id": 4, "level": 3, "parent_id": 2, "name": "Sales Department" }, { "id": 5, "level": 3, "parent_id": 1, "name": "HR Division" }],
    tree = function (data, root) {
        var r, o = {};
        data.forEach(function (a) {
            a.children = o[a.id] && o[a.id].children;
            o[a.id] = a;
            if (a.parent_id === root) {
                r = a;
            } else {
                o[a.parent_id] = o[a.parent_id] || {};
                o[a.parent_id].children = o[a.parent_id].children || [];
                o[a.parent_id].children.push(a);
            }
        });
        return r;
    }(data, 0),
    ul = document.createElement('ul');

[tree].forEach(function iter(level) {
    return function (a) {
        var li = document.createElement('li'),
            div = document.createElement('div'),
            ul,
            l = level;

        this.appendChild(li);
        while (++l < a.level) {
            div.className = 'just-line';
            li.className = 'no-padding';
            li.appendChild(div);
            ul = document.createElement('ul');
            li.appendChild(ul);
            li = document.createElement('li');
            ul.appendChild(li);
            div = document.createElement('div');
        }
        div.appendChild(document.createTextNode(a.name));
        li.appendChild(div);

        if (a.children) {
            ul = document.createElement('ul');
            li.appendChild(ul);
            a.children.forEach(iter(a.level), ul);
        }
    };
}(0), ul);
document.getElementById('tree').appendChild(ul);
.tree li { list-style-type: none; margin: 0; padding: 10px 5px 0 5px; position: relative; }
.tree li::before, .tree li::after { content: ''; left: -20px; position: absolute; right: auto; }
.tree li::before { border-left: 1px solid #999; bottom: 50px; height: 100%; top: 0; width: 1px; }
.tree li::after { border-top: 1px solid #999; top: 30px; width: 25px; }
.tree li div { border: 1px solid #999; display: inline-block; padding: 8px 24px; text-decoration: none; }
.tree li.parent > div { cursor: pointer; }
.tree > ul > li::before, .tree > ul > li::after { border: 0; }
.tree li:last-child::before { height: 30px; }
.tree li.parent > div:hover, .tree li.parent > div:hover + ul li div { background: #f3f3f4; border: 1px solid #94a0b4; color: #000; }
li div.just-line { display: none; }
div.just-line + ul > li::before { padding: 0; border-left: 0; }
div.just-line + ul > li::after { left: -40px; width: 45px; }
.no-padding { padding: 0 !important; }
<div id="tree" class="tree"></div>

Upvotes: 2

Bert Persyn
Bert Persyn

Reputation: 411

For coding I would opt for a MVC framework like AngularJS

Example: http://jsfiddle.net/brendanowen/uXbn6/8/

The only thing you would need to do is create a datamodel that resembles the structure. Yours is flat, I would opt to have tree like data, it's much easier to fit into a MVC like framework ;)

[{"id": 1,"level":1,"parent_id": 0, "name": "Board of Director", childs: [{"id": 2, ..., childs: [{}]}}];

Upvotes: 0

Keith
Keith

Reputation: 24191

Seen as the DOM is already a tree, we can take advantage of this fact.

The below I think does what your after.

var data = [{
   "id": 1,
   "level":1,
   "parent_id": 0,
   "name": "Board of Director"
}, {
   "id": 2,
   "level":2,
   "parent_id": 1,
   "name": "Finance Division"
}, {
   "id": 3,
   "level":3,
   "parent_id": 2,
   "name": "Finance Department"
}, {
   "id": 4,
   "level":3,
   "parent_id": 2,
   "name": "Sales Department"
}, {
   "id": 5,
   "level":3,
   "parent_id": 1,
   "name": "HR Division"
}];

document.addEventListener("DOMContentLoaded", function(event) { 
  var 
    qsa = document.querySelectorAll.bind(document),
    ce = document.createElement.bind(document);   
  var rootparent = qsa('.tree UL')[0]; 
  rootparent.innerHTML = '';
  data.forEach(function (e) {
    var parent = qsa('[data-id=\'' + e.parent_id+'\'] UL')[0] 
      || rootparent;
    var li = ce("LI");    
    var div = ce('DIV');
    var ul = ce('UL');
    li.appendChild(div);
    li.appendChild(ul);
    li.setAttribute('data-id', e.id);
    div.textContent = e.name;    
    parent.appendChild(li);
  });
});
.tree li {
  list-style-type: none;
  margin: 0;
  padding: 10px 5px 0 5px;
  position: relative
}
.tree li::before,
.tree li::after {
  content: '';
  left: -20px;
  position: absolute;
  right: auto
}
.tree li::before {
  border-left: 1px solid #999;
  bottom: 50px;
  height: 100%;
  top: 0;
  width: 1px
}
.tree li::after {
  border-top: 1px solid #999;
  top: 30px;
  width: 25px
}
.tree li div {
  border: 1px solid #999;
  display: inline-block;
  padding: 8px 24px;
  text-decoration: none
}
.tree li.parent > div {
  cursor: pointer
}
.tree > ul > li::before,
.tree > ul > li::after {
  border: 0
}
.tree li:last-child::before {
  height: 30px
}
.tree li.parent > div:hover,
.tree li.parent > div:hover + ul li div {
  background: #f3f3f4;
  border: 1px solid #94a0b4;
  color: #000
}
li div.just-line {
  display: none;
}
div.just-line + ul > li::before {
  padding: 0;
  border-left: 0;
}
div.just-line + ul > li::after {
  left: -40px;
  width: 45px;
}
.no-padding {
  padding: 0 !important;
}
<div class="tree">
  <ul>
  </ul>
</div>

Upvotes: 0

Related Questions