Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Orgchart With CSS Flex and ZK

DZone's Guide to

Orgchart With CSS Flex and ZK

We create a rather common application, an org chart, on the client-side using ZK to make working with HTML, CSS, and JS easier.

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

I invested a little time to "play" with CSS Flex to see if/how a simple organization chart can be styled using CSS Flex, mostly without CSS.

In order to create the hierarchy dynamically, I'll show how ZK Features can be leveraged - even though the example HTML/CSS are not ZK Framework specific and can be generated by any framework of your choice.

Flexible Orgchart Markup

The starting point is an ordinary, unordered html-lists (<ul>/<li>as they conveniently allow nesting arbitrary hierarchies.

orgchart-raw.zul

<div xmlns="xhtml">
  <ul>
    <li>
      Company
      <ul>
        <li>
          Sales Department
          <ul>
            <li>Sales Unit I</li>
            <li>Sales Unit II</li>
          </ul>
        </li>
        <li>Purchase Department</li>
        <li>
          Controlling
          <ul>
            <li>Controlling Unit I</li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

The browser will render something like the image below, already conveying the information needed - like in the good old '90s.

Orgchart Raw

An orgchart is structurally the same: a Tree. We "just" need to align the items horizontally and connect the nodes somehow. There are definitely several libraries available using JavaScript/Canvas/SVG that will do the job.

The challenge here is: how far can we get with pure HTML/CSS for the layout - to ease markup generation with any templating engines or UI frameworks.

With the previous case, we can do this: (still coder's "art", nicer examples will follow)

Image title

As you can see the nodes consume the available space. The text is allowed to line-break when necessary to avoid a horizontal scrollbar as long as possible. The connecting edges adjust without JS (using responsive CSS media queries we could adjust even further but that's a bit too much for this article).

Let's see how this is done:

In order to add the styles, orgchart-simple.zul makes some minor adjustments to the markup, besides adding the CSS stylesheets.

<?style src="css/orgchart.css"?>
<?style src="css/simple.css"?>
<!--<?style src="css/debug.css"?>-->
<zk>
  <div xmlns="xhtml" class="centered">
    <ul class="orgchart">
      <li class="root">
        <div class="nodecontent">Company</div>
        <ul>
          <li>
            <div class="nodecontent">Sales Department</div>
            <ul>
              <li class="leaf">
                <div class="nodecontent">Sales Unit I</div>
              </li>
              <li class="leaf">
                <div class="nodecontent">Sales Unit II</div>
              </li>
              ...

(Don't be distracted by ZK's syntax to add stylesheets and the XHTML namespace declaration - the HTML/CSS is what's relevant right now).

The main differences (besides the added stylesheets) are the CSS classes for the root and leaf nodes, and the divs with class nodecontent

simple.css just defines the nodecontent classes - here a typical flat box with round corners.

orgchart.css is arranging the  ul/li elements as a tree - taking into account any size the.nodecontent-divs might have.

The relevant CSS rules to arrange the nodes for the tree structure are:

/*align children horizontally using CSS flex*/
.orgchart ul {
    display: flex;
}

/*align nodecontent and children list vertically*/
.orgchart li {
    display: flex;
    flex-direction: column;
    position: relative;
}

/*arrange the nodecontent centered above the children list*/
.orgchart .nodecontent {
    align-self: center;
    position: relative;
    margin: 20px 5px;
}

After adding some debugging styles (debug.css) the layout nesting becomes visible.

Image title

The ul elements are surrounded by a dark blue border (showing the horizontal arrangement).
The li elements are green highlighting their adaptive widths.
The pink borders surround the .nodecontent-divs which are centered horizontally while line breaks increase the height if needed.

Furthermore, the boxes also expose how the connectors work. Relative to the size of containing  li-elements, the connecting lines are mostly located at very basic positions (center, left or right) and have a fixed height covering the 20px margin of the .nodecontent-divs. Their width is either 100% or adaptive calc(50%+1px) (1px because of the border width of 2px). These relative widths allow an arbitrary combination of node sizes and a flexible number of children without JS calculations.

The connecting lines are rendered using partial borders of CSS pseudo elements ::before and  ::after. Below an excerpt of orgchart.css with the rules for the vertical top and bottom connectors.

.orgchart .nodecontent::after,
.orgchart .nodecontent::before {
    box-sizing: border-box;
    content: '';
    position: absolute;
    z-index: -1;
    border: 0px solid #CCCCCC;
}

...

.orgchart .nodecontent::after,
.orgchart .nodecontent::before {
    border-left-width: 2px;
    width: 2px;
    height: 20px;
    left: calc(50% - 1px);
}

.orgchart .nodecontent::before {
    top: -20px;
}
.orgchart .nodecontent::after {
    bottom: -20px;
}

The full stylesheet (orgchart.css) contains additional rules to hide connectors for root and leaf nodes, as well as the curved connectors (using border-radius) for the :first-child and :last-child li-elements.

.orgchart li:first-child::before {
    width: calc(50% + 1px);
    right: 0;
    border-left-width: 2px;
    border-top-left-radius: 7px;
}

.orgchart li:last-child::before {
    width: calc(50% + 1px);
    border-right-width: 2px;
    border-top-right-radius: 7px;
}

These styles are quite fine-tuned. They would definitely benefit from a CSS preprocessor such as LESS or SASS to replace recurring numbers such as the 20px height or the 2px line width. Also, they cause some sub-pixel misalignments at certain odd zoom levels (e.g. 190% varying between browsers) - however the picture never gets destroyed by zooming in/out and the connections remain stable.

Scaling Up a Bit

Obviously, the previous example was only suitable to demonstrate the basics and you'd rarely need an orgchart for such a trivial company hierarchy anyway. So how about this instead:

orgchart-bigger.zul

Image title

(source Wikipedia)

Using the same styles as above you can see the limitations quite clearly: it may be suitable for ultra-wide screens but it doesn't really take advantage of the screen height - and with orgcharts we often have those vertical structures in mind anyway - e.g. team members below a supervisor.

In addition, to save space, it would be nice to have something like this (a fully squeezed version of orgchart-vertical.zul)

Image title

Now the same information is represented in a more compact way. The only difference is an additional optional stylesheet (orgchart-vertical.css) which contains slightly more complicated rules for the connectors of vertical nodes.

The HTML simply requires a CSS class to apply to the vertical layout for a specific node and children list.

<ul class="orgchart">
  <li class="root">
    <div class="nodecontent">President</div>
    <ul>
      <li class="node">
        <div class="nodecontent">Vice President <br/> Account Services</div>
        <ul>
          <li class="vertical">
            <div class="nodecontent">Account Supervisor</div>
            <ul>
              <li class="leaf">
                <div class="nodecontent">Account Executive</div>
              </li>
              <li class="leaf">
                <div class="nodecontent">Account Executive</div>
              </li>
            </ul>
          </li>
          ...

In line 8, the class="vertical" is the only thing that needs to be added in order to arrange the children of this node vertically. The same style class is added for the remaining 3 supervisor nodes in this chart.

The current styles only address leaf nodes below a vertical node - any further nesting will break the layout. I am sure this would be technically possible - for the sake of brevity and readability I stopped there.

Dynamic Rendering

This section is about rendering the chart using a Java object model and ZK's shadow elements with templates. Similar solutions will exist for other templating engines as long as they generate the same markup. If you're just interested in the final colorful result skip to the end and just inspect the generated HTML in the browser.

Model/Template-Based Rendering

The model consists of OrgNode and OrgNodeData objects. The former contains the children collection, the isLeaf and isVertical properties as well as a generic the data object.

public class OrgNode<T> {
    private T data;
    private List<OrgNode<T>> children;
    /*constructors omitted*/
    public boolean isLeaf() { ... }
    public boolean isVertical() { ... }
    public T getData() { return data; }
    public List<OrgNode<T>> getChildren() { return children; }
}

The latter (OrgNodeData) holds the actual information to render as the node content: title, name, icon, type (used for node-specific styling). 

public class OrgNodeData {
    public enum Type {PRESIDENT, VICE_PRESIDENT, SUPERVISOR, EMPLOYEE}

    private Type type;
    private String title;
    private String name;
    private String icon;
    ...

Using this simple structure we can represent the organization hierarchy:

OrgChartViewModel.java

public class OrgChartViewModel {
    private static final String NO_IMAGE = null;
    private OrgNode<OrgNodeData> orgChartRoot;

    @Init
    public void init() {
        orgChartRoot = createOrgNode(PRESIDENT, "President", null, "icon/icon1.svg",
            createOrgNode(VICE_PRESIDENT, "Vice President", "Account Services", "icon/icon2.svg",
                createOrgNode(SUPERVISOR, "Account Supervisor", null, NO_IMAGE,
                    createOrgNode(EMPLOYEE, "Account Executive", null, NO_IMAGE),
                    createOrgNode(EMPLOYEE, "Account Executive", null, NO_IMAGE)
                ),
                createOrgNode(SUPERVISOR, "Account Supervisor", null, NO_IMAGE)
            ),
            createOrgNode(VICE_PRESIDENT, "Vice President", "Creative Services", "icon/icon3.svg",
            ...
    }

    public OrgNode<OrgNodeData> getOrgChartRoot() { return orgChartRoot; }

    public static OrgNode<OrgNodeData> createOrgNode(
            OrgNodeData.Type type, String title, String name, String icon, 
            OrgNode<OrgNodeData>... children) { ... }
}

The helper function createOrgNode(...) simplifies composing in the whole hierarchy in a single statement. No one is stopping you from building the hierarchy in a different way. All we then need is to expose the root node via getOrgChartRoot() and let UI templates consume the hierarchy recursively.

orgchart-template.zul

<x:ul class="orgchart" xmlns:x="xhtml">
  <apply template="node" node="${root}" root="${true}"/>
  <template name="node">
    <x:li class="${root ? ' root' : ''}${node.leaf ? ' leaf' : ''}${node.vertical ? ' vertical' : ''}">
      <x:div class="nodecontent">
        <if test="${collapsible}">
          <apply template="collapsible" node="${node}"/>
        </if>
        <apply template="nodecontent" data="${node.data}"/>
      </x:div>
      <if test="${not empty node.children}">
        <x:ul>
          <forEach items="${node.children}">
            <apply template="node" node="${each}" root="${false}"/>
          </forEach>
        </x:ul>
      </if>
    </x:li>
  </template>
</x:ul>

This template is ZK specific and uses several features like shadow/xhtml elements and EL expressions to add contextual CSS classes: root, leaf, vertical (the collapsible part is explained in the next paragraph).

You'll find all previous elements (ul/li/div.nodecontent) controlled by ZK's shadow elements  (if/forEach/apply). The recursion happens in line 14 only if the current node has children. This "node" template only cares about laying out the hierarchy and doesn't contain any nodecontent specific information making it reusable for user-defined nodecontent markup - also no property of the OrgNodeData object is accessed allowing a different class representing the node content.

In orgchart-model.zul, the nodecontent-template is defined and injected into <orgchart>.

<?style src="css/orgchart.css"?>
<?style src="css/orgchart-vertical.css"?>
<?style src="css/simple.css"?>
<!--<?style src="css/debug.css"?>-->
<?component name="orgchart" templateURI="template/orgchart-template.zul"?>
<zk>
  <div sclass="centered" 
       viewModel="@id('vm') @init('zk.example.template.orgchart.OrgChartViewModel')">
    <orgchart root="@init(vm.orgChartRoot)">
      <template name="nodecontent">
        <div>
          <label value="${data.title}"/>
          <if test="${!empty data.name}">
            <separator/>
            <label value="${data.name}"/>
          </if>
        </div>
      </template>
    </orgchart>
  </div>
</zk>

Line 9 calls the orgchart-template, providing the markup (to be rendered inside the  .nodecontent-div) and the root note root="@init(vm.orgChartRoot)" from the viewModel.

For different node styling, a different template can be provided as can be seen in orgchart-full.zul.

<orgchart root="@init(vm.orgChartRoot)" collapsible="true">
  <template name="nodecontent">
    <div sclass="orgnode ${data.type}">
      <if test="${!empty data.icon}">
        <div sclass="icon" style="${('background-image: url(\'' += data.icon += '\')')}"/>
      </if>
      <label value="${data.title}"/>
      <if test="${!empty data.name}">
        <separator/>
        <label value="${data.name}"/>
      </if>
    </div>
  </template>
...
</orgchart>

Above, the node type is used as a styleclass for individual node styles, and, if present, an icon is prepended as well.

Collapsible Nodes

A simple way to collapse and expand nodes is by just hiding/showing the children using JS (finally some JavaScript).

orgchart-model-collapsible.zul

<zk xmlns:w="client">
  <script>
    function toggleNode(wgt) {
      jq(wgt).closest('li').toggleClass('collapsed');
      jq(wgt).find('i').toggleClass('z-icon-minus z-icon-plus');
    }
  </script>
  <div sclass="centered" viewModel="@id('vm') @init('zk.example.template.orgchart.OrgChartViewModel')">
    <orgchart root="@init(vm.orgChartRoot)" collapsible="true">
      <template name="nodecontent">...</template>

      <template name="collapsible">
        <if test="${not node.leaf}">
          <a sclass="collapse" iconSclass="z-icon-minus" w:onClick="toggleNode(this);"/>
        </if>
      </template>
    </orgchart>
  </div>
</zk>

Setting the flag collapsible="true" on <orgchart> (Line 9) will invoke the collapsible-template for each node. In Line 13, I specified to only render the collapse toggle for non-leaf nodes.

All it renders is an <a>-element with a client-side listener w:onClickwhich calls a trivial JS function to toggle style classes (jq is ZK's alias for jQuery's  $-function). The styles for the collapse icon are defined in both simple.css or full.css to allow custom styling/positioning of the toggle in case this is needed.

.nodecontent .collapse {
    position: absolute;
    bottom: -15px;
    left: calc(50% - 6px);
    z-index: 0;
    color: white;
    background-color: #CCCCCC;
    border-radius: 6px;
    height: 12px;
    width: 12px;
    font-size: 12px;
    text-align: center;
}

Also, here, the positioning is adaptive to the actual nodecontent dimensions: centered below the nodecontent.

If we wanted we could control the open/close state in the OrgNode object and bind a server-side command-handler to load additional nodes on demand (here I didn't, but good to know we have the option).

Fully Styled Example

Sometimes you're lucky and you get some nice design templates from a dedicated designer (unfortunately this is an often forgotten role).

By styling (see: full.css) just the inner .orgnode-divs we get the below screenshot, which makes a big difference in appearance, even though the overal implementation remains the same:

orgchart-full.zul

Image title

Our previous efforts - ensuring the connections are independent of the actual node size - support these irregular shapes. Keep in mind that the nodes are still just treated like ordinary rectangular divs (again notice the automatic line breaks fitting into the available space where possible).

Image title

Wrapping Up

I hope this article was insightful for you. For me, it was definitely an enjoyable little distraction to explore the possibilities of a (finally) widely adopted CSS feature.

Example and Source

The source code (among other templating examples) is available on github/zkoss-demo/zk-template-examples project and contains instructions on how to run the project from command line.

Once the example project is running you can access the example pages for this article below:

http://localhost:8080/zk-template-examples/orgchart/

e.g.: http://localhost:8080/zk-template-examples/orgchart/orgchart-full.zul

Browser Compatibility

Obviously, the example uses CSS flexbox so it should support all modern browsers. On IE11 I noticed that the automatic line breaks don't happen automatically so that a horizontal scrollbar is added earlier - still, the whole chart is accessible. If anyone knows how that can be enabled for IE11 too, I'd be happy for a helpful comment.

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:
flexbox ,zk framework ,web dev ,client-side

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}