Columnize everything!

Millery is a multi-purpose Miller Columns plugin, built using jQuery library. Supports HTML Lists, JSON static files, JSON API results, and will support more. Uses SCSS files for templating. Grunt for automation.

Get it from CodeCanyon

Features


Millery is a "Miller Columns" implementation using jQuery library. Miller Columns are also known as "Cascading Lists" and it's a browsing/visualization technique that can be applied to nested (tree) structures. It was invented by Mark S. Miller at Yale University and named after him. (information taken from Wikipedia: https://en.wikipedia.org/wiki/Miller_columns)

Miller Columns

It's like a multi level select box, but it's not!

Responsive

Every device has it's own display, and it's adaptive to window size

Smooth Animations

Just look at the transition between columns!

Saving last state

It remembers where you left when you came back!

Breadcrumbs

You'll never lose your way!

Multiple Sources

You can use a web service, or a static JSON file, or even HTML markup!

Searchable

You can find the path easily to specific items.

Keyboard Navigation

You can navigate through nodes via keyboard.

Customizable

You can modify all the behaviour like hiding some part, disabling some feature etc.

Ready to use themes

You can choose between 5 themes, or create your own!

SASS Support

You can re-design completely using SASS!

Grunt Automated

Yes, you can automatically build using Grunt!

Icons (except Sass and Grunt logo) made by Freepik from www.flaticon.com is licensed by CC 3.0 BY



How to use it


The main files of this plugin are millery.min.js and millery.min.css, which are generated by compiling and minifying millery.js and millery.scss. The plugin relies on jQuery library, also needs to be included before including millery.min.js.

The font used on this plugin is Roboto, and the icons are provided by Font Awesome which are included remotely from their CDN's. You can change it anyway you want.

The example configuration which can be found in the base index file is:

<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,500,700" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/jquery-2.1.4.min.js" type="text/javascript"><\/script>');
</script>
<!-- millery includes -->
<link href="build/css/millery.min.css" rel="stylesheet"/>
<script src="build/js/millery.min.js"></script>
<!-- end millery includes -->


Then, to attach a Millery object to an element you desire, use this:

$(".millery").millery({ options });


You can customize your plugin with options you provide to the initializing function,

$(".millery").millery({visibleColumns: 4, showHeaders: false});


or the element's data-plugin-options property.

<div class="millery" data-plugin-options='{"visibleColumns":4,"showHeaders":false}'></div>


Once initalized, you can access the millery object from the .millery-container like this:

var millery = $(".millery:eq(3) .millery-container").data("millery");
millery.config.visibleColumns = 3;

Properties


Once you've got the millery instance object, you can access these properties:

  • elem [DOM node object]: The HTML dom node which the instance is created on.
  • $elem [jQuery Object]: jQuery object of the dom node. Equals to $(millery.elem)

For example,

var millery = $("#elem1").millery();
// millery.elem will be equal to document.getElementById("elem1") or $("#elem1").get(0);
// millery.$elem will be equal to $("#elem1");


  • config [JS Object]: The instance's configuration object. Consists of merging default values and user defined settings. The details of it is explained in the Options section.
  • globals [Javascript object]: plugin internals, you can alter them if you know what they're doing. You can get a complete list if you look at the source code.

    These properties are also defined, but not recommended to tinker on.

  • container [jQuery Object]: This refers to the outermost container element of the instance dropdown or inline instance and the input itself.

  • header [jQuery Object]: This refers to the header part of the plugin.
  • footer [jQuery Object]: This refers to the footer part, - not the mobile buttons part - the ranges part of the plugin.
  • input [jQuery Object]: This refers to the first child of the container, which contains everything except the input which is caleran instantiated on.

Configuration


These are the options you can set with the javascript plugin initialization object, or with the data-plugin-options html attribute on the input element, or within the events by directly modifying the instance.config object.

  • source [jQuery Object]: If the source will be pulled from a DOM UL element, here will it be assigned. Like $("#myAwesomeColumns"). Default: null

  • visibleColumns [integer]: The visible columns on the UI. If the column length is smaller then the visible columns setting, it'll be shown blank. If it exceeds it, the columns will scroll to the active column. Default: 3

  • panelWidth [integer]: The details pane width. Accepts any CSS length type like 'px', 'em', '%' etc. Default: 75%

  • panelType ['overlay'|'always'|'modal'|null]: Details pane display type. Shows when a childless node (leaf node) is clicked. Default: null
    • overlay: Displays as a sliding panel from the right
    • always: Shows as an always visible area on the right
    • modal: Shows as an overlay modal sliding from top
    • null: Shows nothing

  • transitionDuration [integer]: the animation duration in milliseconds when opening the modal, transitioning between columns etc., default: 100

  • showHeaders [boolean]: show or hide the column headers, default: true

  • searchable [boolean]: defines if the search functionality will be implemented on the instance. Will be explained on the examples section. default: true

  • columnSearch [boolean]: adds an input on each column below their headers to enable instant search on column node texts. default: true

  • keepState [boolean]: If defined, the instance keeps it's last path and moves right to that node on instantiation. Uses local storage. default: true

  • stateSaveId [string]: If you are planning to use multiple instances on the same page, this acts like an ID attribute on each instance for saving it's state. default: null

  • enableKeyboard [boolean]: Enables or disables keyboard navigation on nodes. default: true

  • searchLabel [string]: The placeholder on the search input. default: Search...

  • backButtonLabel [string]: The text displayed on the back button. default: Back

  • closeButtonLabel [string]: The text displayed on the close button, shown when a panel/modal/overlay is opened. default: Close

  • columns [object array]: the column definitions of the instance. Creates the skeleton of the table. Structured as follows:

    • columns.header [string|function]: defines the column header. Can be directly set as a string, or if you want to use the data from your AJAX result or JSON file, you can use a callback to build it. For example:

        columns: [
            {
                header: "Column Header #1",
                ..
            }
        ]
      
        // or as function
      
        columns: [
            {
                header: function(data){
                    // data will be the last node data of this column to be an example
                }
            }
        ]
      


    • columns.sourceType [string]: This will be "json" for now to use the plugin with JSON sourced data (for static JSON files or dynamic ajax calls), later other methods may be implemented.

    • columns.source [string]: URL for retrieving the column data. May use placeholders like %id% to replace with the formatUrl method.

    • columns.dontCheckParent [boolean]: When using a single JSON file for multiple columns (suppose you have a Parent ID field set on the children), you should have this setting as false and parentIdField value set to properly structure the tree view. If you are using an AJAX call or separate JSON files for each levels and child nodes, you can set it to true to load all retrieved data directly to the column without parent filtering.

    • columns.loopPath [string]: The root node for the results to loop over if you have your children on some other node instead. For example, if you have this structure on the data response:

        {
            count: 15,
            status: "ok",
            data: {
                albums: [
                    // children
                ]
            }
        }
      


      then your root node (loopPath) would be: data.albums. It accepts dot notation.

    • columns.idField [string]: The object member to use as the identifier for each node

    • columns.paginated [boolean]: Whether to use pagination in the column (not yet implemented)
    • columns.pageCount [integer]: When pagination is active, items in one page (not yet implemented)
    • columns.labelField [string]: The object member name to use as the label, the visible text on the node
    • columns.searchParam [string]: The request parameter name to make a search query, will be appended to the source parameter.
    • columns.childrenCountField [string]: The field name or a string returning function(obj) to get the children count, if the node has no children, it'll be treated as a leaf node (it is not a parent node), else it'll be accepted as a parent node. (the arrow will be displayed and will try to load it's child nodes on click.)

    • columns.parentIdField [string]: The field name of the parent ID container, used with dontCheckParent: false to filter the children nodes on a single JSON file or JSON response

    • columns.format [function]: Formats the node's displayed HTML, can be used to add icons, buttons etc. Usage:

        format: function (value, obj){
            return "<span class='id'>" + obj.id + "</span><span clas='value'>" + obj.name + "</span>";
            // or `return value;` directly
        }
      


    • columns.formatUrl [function]: URL formatter, used to replace the placeholders. Usage:

        formatUrl: function(url, obj){
            // url argument will contain the source parameter of the column configuration
            // obj argument will contain the data of the current clicked node
            // suppose url is defined like "/albums/%id%/songs", then we can use it this way
            return url.replace(/%id%/, obj.id);
        }
      


Available Events


  • onbeforeinit [function]: Event which is triggered before all the initialization routines are executed. You can change the configuration which need to be modified before initialization here.

    Prototype:

      onbeforeinit: function(millery){
          // millery: millery plugin instance
      }
    


  • oninit [function]: Event which is triggered after the initialization routines are executed and the plugin is ready to serve the users.

    Prototype:

      oninit: function(millery){
          // millery: millery plugin instance
      }
    


  • onnodeclick [function]: Event which is triggered on click on a leaf node. Needs a boolean return value. If you return false to this function, it will prevent the panel to be opened, otherwise it will open the panel.

    Prototype:

      onnodeclick: function(millery, node, nodedata){
          // millery: millery plugin instance
          // node: the DOM node which is clicked
          // nodedata: the data attached to this DOM node
          return true; // false prevents panel open
      }
    


  • onbeforeappend [function]: event which is triggered before appending a column to the interface. Needs a boolean return value. If false is returned, the column won't be created on the interface.

    Prototype:

      onbeforeappend: function(millery, column, def){
          // millery: millery plugin instance
          // column: created column DOM element
          // def: column configuration
          return true; // false prevents attachment
      }
    


  • onafterappend [function]: event which is triggered after appending the column on the interface. It is called after the animation ends. You can later modify the appended column contents in this event.

    Prototype:

      onafterappend: function(millery, column, def){
          // millery: millery plugin instance
          // column: created column DOM element
          // def: column configuration
      }
    


  • onbeforepanelclose [function]: event which is triggered before the panel close happens. Returning false will prevent the panel to be closed.

    Prototype:

      onbeforepanelclose: function(millery, panel){
          // millery: millery plugin instance
          // panel: panel DOM jQuery object
          return true; // false prevents panel closing
      }
    


  • onafterpanelclose [function]: event which is triggered after the panel close happens.

    Prototype:

      onafterpanelclose: function(millery, panel){
          // millery: millery plugin instance
          // panel: panel DOM jQuery object
      }
    


  • onbeforepanelopen [function]: event which is triggered before the panel opening happens. Returning false will prevent the panel to be opened.

    Prototype:

      onbeforepanelopen: function(millery, panel){
          // millery: millery plugin instance
          // panel: panel DOM jQuery object
          return true; // false prevents panel opening
      }
    


  • onafterpanelopen [function]: event which is triggered after the panel opening happens.

    Prototype:

      onafterpanelopen: function(millery, panel){
          // millery: millery plugin instance
          // panel: panel DOM jQuery object
      }
    


  • onbeforeremove [function]: event which is triggered before removing column(s) from the interface. Multiple columns will be removed for example, when returning from level 3 to level 1 which can happen by clicking a node on level1, while the active node is in level 3. Returning false to this function will prevent panel removal.

    Prototype:

          onbeforeremove: function(millery, columnsToRemove){
              // millery: millery plugin instance
              // columnsToRemove: jQuery object collection of panels
              return true; // false prevents update
          }
    


  • onafterremove [function]: event which is triggered after a column removal occurs on the instance.

    Prototype:

          onafterremove: function(millery){
              // millery: millery plugin instance
          }
    


  • onbackbutton [function]: event which is triggered when user clicks the back button on the navigation bar. Returning false will prevent the back button functions to be executed (Returning back will be prevented.)

    Prototype:

          onbackbutton: function(millery){
              // millery: millery plugin instance
              return true; // false prevents returning back
          }
    


  • onbreadcrumb [function]: event which is triggered when user clicks a link in the breadcrumbs. If you return false, it prevents switching to that panel. Otherwise you need to return true.

    Prototype:

      onbreadcrumb: function(millery, breadcrumb){
          // millery: millery plugin instance
          // breadcrumb: clicked breadcrumb
          return true; // false prevents switching
      }
    

Methods

  • setPanelData [function]: method which enables you to define the text/html inside the opened modal/panel. Mostly used in the onnodeclick event.

    Usage:

      instance.setPanelData("Here will be an <strong>interesting</strong> content");
    


  • openPanel [function]: Shows the inner panel/modal.

    Usage:

      instance.openPanel();
    


  • closePanel [function]: Hides the inner panel/modal.

    Usage:

      instance.closePanel();
    


TODO:

There aren't many methods yet you will need to use, but later

  • adding/removing nodes
  • switching to nodes
  • destroying the instance

will be implemented.

Examples


Simple initialization with HTML Unordered List


  • Item 1
  • Item 2
    • Item 2.1
    • Item 2.2
  • Item 3
    • Item 3.1
    • Item 3.2
      • Item 3.2.1
      • Item 3.2.2
    • Item 3.3
  • Item 4
  • Item 5
<ul id="millery-example-1">
    <li>Item 1</li>
    <li>Item 2
        <ul>
            <li>Item 2.1</li>
            <li>Item 2.2</li>
        </ul>
    </li>
    <li>Item 3
        <ul>
            <li>Item 3.1</li>
            <li>Item 3.2
                <ul>
                    <li>Item 3.2.1</li>
                    <li>Item 3.2.2</li>
                </ul>
            </li>
            <li>Item 3.3</li>
        </ul>
    </li>
    <li>Item 4</li>
    <li>Item 5</li>
</ul>
<div id="millery-1" class="millery millery-theme-1"></div>
$("#millery-1").millery({
    source: $("#millery-example-1"),
    panelType: "modal",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    }
});


Simple initialization with separate JSON files for each column


<div id="millery-2" class="millery millery-theme-1"></div>
$("#millery-2").millery({
    panelType: "modal",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-1.json",
        idField: "id",
        parentIdField: null,
        labelField: "label",
        childrenCountField: "children",
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-2.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-3.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        format: function (value, obj) {
            return value;
        }
    }]
});


Simple initialization with single JSON file for all columns


Note that the parent ID field (parentIdField) variable must be defined in all column definitions for this to work, otherwise it will show all nodes in the first column.

<div id="millery-3" class="millery millery-theme-1"></div>
$("#millery-3").millery({
    panelType: "modal",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        labelField: "label",
        searchParam: "query",
        childrenCountField: "children",
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        format: function (value, obj) {
            return value;
        }
    }]
});


Simple initialization with JSON web service


<div id="millery-4" class="millery millery-theme-1"></div>
$("#millery-4").millery({
    panelType: "modal",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "Countries",
        sourceType: "json",
        loopPath: "geonames",
        source: "https://api.geonames.org/countryInfoJSON?username=rettica",
        dontCheckParent: true,
        idField: "geonameId",
        labelField: "countryName",
        childrenCountField: "population", // service doesn't have a children field, we're faking it
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "States",
        sourceType: "json",
        source: "https://api.geonames.org/childrenJSON?geonameId={id}&username=rettica",
        idField: "geonameId",
        loopPath: "geonames",
        dontCheckParent: true,
        childrenCountField: "lat", // service doesn't have a children field, we're faking it
        labelField: "name",
        format: function (value, obj) {
            return value;
        },
        formatUrl: function(url, obj) {
            return url.replace(/{id}/, obj.geonameId);
        }
    },
    {
        header: "Cities",
        sourceType: "json",
        source: "https://api.geonames.org/childrenJSON?geonameId={id}&username=rettica",
        idField: "geonameId",
        loopPath: "geonames",
        dontCheckParent: true,
        childrenCountField: "lat", // service doesn't have a children field, we're faking it
        labelField: "name",
        format: function (value, obj) {
            return value;
        },
        formatUrl: function(url, obj) {
            return url.replace(/{id}/, obj.geonameId);
        }
    },
    {
        header: "Districts",
        sourceType: "json",
        source: "https://api.geonames.org/childrenJSON?geonameId={id}&username=rettica",
        idField: "geonameId",
        loopPath: "geonames",
        dontCheckParent: true,
        childrenCountField: "",
        labelField: "name",
        format: function (value, obj) {
            return value;
        },
        formatUrl: function(url, obj) {
            return url.replace(/{id}/, obj.geonameId);
        }
    }]
});


Changing visible columns


<div id="millery-5" class="millery millery-theme-1"></div>
$("#millery-5").millery({
    panelType: "modal",
    visibleColumns: 1,
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-1.json",
        idField: "id",
        parentIdField: null,
        labelField: "label",
        searchParam: "query",
        childrenCountField: "children",
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-2.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        format: function (value, obj) {
            return value;
        }
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-3.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        format: function (value, obj) {
            return value;
        }
    }]
});


Panel Type "always"


<div id="millery-6" class="millery millery-theme-1"></div>
$("#millery-6").millery({
    panelType: "always",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-1.json",
        idField: "id",
        parentIdField: null,
        labelField: "label",
        childrenCountField: "children",
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-2.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-3.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    }]
});


Panel Type "overlay"


<div id="millery-7" class="millery millery-theme-1"></div>
$("#millery-7").millery({
    panelType: "overlay",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-1.json",
        idField: "id",
        parentIdField: null,
        labelField: "label",
        childrenCountField: "children"
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-2.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-3.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    }]
});


Search Functionality


This is enabled by setting searchable to true. Also searchParam on each column needs to be defined to append the query to the URL. If the search url is different from the source url, you can define the url as searchUrl. This way, when a node is clicked, this parameter will be appended to the query string. This feature needs to be implemented by the user on the backend side using the requests made by the plugin.

<div id="millery-8" class="millery millery-theme-1"></div>
$("#millery-8").millery({
    panelType: "overlay",
    searchable: true,
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        labelField: "label",
        searchParam: "query",
        searchUrl: "https://rettica.com/millery/docs/search.php?parent={parent}",
        childrenCountField: "children",
        formatUrl: function(url, obj) {
            return url.replace(/\{parent\}/, obj ? obj.id : "");
        }
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        searchUrl: "https://rettica.com/millery/docs/search.php?parent={parent}",
        formatUrl: function(url, obj) {
            return url.replace(/\{parent\}/, obj.id);
        }
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        searchParam: "query",
        searchUrl: "https://rettica.com/millery/docs/search.php?parent={parent}",
        formatUrl: function(url, obj) {
            return url.replace(/\{parent\}/, obj.id);
        }
    }]
});
<?php
    // search backend example

    /**
     * Recursive tree filtering method
     * Returning rows affected by the query with their parents
     *
     * @param     array     $arr    input array
     * @param    string    $parent    the parent node to traverse from
     * @param    string    $query    search parameter
     *
     * @return     array    $filtered    the results containing the matching nodes and their parents up to $parent node
     **/
    function filterTree($arr, $parent, $query)
    {
        $filtered = [];

        $roots = array_filter($arr, function ($item) use ($parent) {
            return $item->parent == $parent;
        });

        foreach ($roots as $root) {
            if (stripos($root->label, $query) !== false) {
                $filtered[] = $root;
            } else {
                $results = filterTree($arr, $root->id, $query);
                if (count($results) > 0) {
                    $filtered = array_merge($filtered, $results);
                    $filtered[] = $root;
                }
            }
        }

        return $filtered;
    }

    $contents = json_decode(file_get_contents("millery-data-single.json"), false);
    $parent = isset($_GET["parent"]) ? $_GET["parent"] : null;
    $query = isset($_GET["query"]) ? $_GET["query"] : "";

    $results = filterTree($contents, $parent, $query);

    echo json_encode($results);
?>


Customizing Node Display


<div id="millery-9" class="millery millery-theme-1"></div>
$("#millery-9").millery({
    panelType: "modal",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        labelField: "label",
        childrenCountField: "children",
        format: function (value, obj) {
            if(obj.children > 0)
                return "<img src='assets/folder.png' class='millery-node-icon'>&nbsp;" + value;
            else
                return "<img src='assets/file.png' class='millery-node-icon'>&nbsp;" + value;
        }
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        format: function (value, obj) {
            if(obj.children > 0)
                return "<img src='assets/folder.png' class='millery-node-icon'>&nbsp;" + value;
            else
                return "<img src='assets/file.png' class='millery-node-icon'>&nbsp;" + value;
        }
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label",
        format: function (value, obj) {
            if(obj.children > 0)
                return "<img src='assets/folder.png' class='millery-node-icon'>&nbsp;" + value;
            else
                return "<img src='assets/file.png' class='millery-node-icon'>&nbsp;" + value;
        }
    }]
});


Customizing Header Display


<div id="millery-10" class="millery millery-theme-1"></div>
$("#millery-10").millery({
    panelType: "modal",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: function(){
            return "<div style='display: flex; flex: 1;'><div style='flex: 1; text-align: left;'>First Column</div><div><button type='button'><i class='fa fa-plus'></i></button></div></div>";
        },
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        labelField: "label",
        childrenCountField: "children"
    },
    {
        header: function(){
            return "<div style='display: flex; flex: 1;'><div style='flex: 1; text-align: left;'>Second Column</div><div><button type='button'><i class='fa fa-plus'></i></button></div></div>";
        },
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: function(){
            return "<div style='display: flex; flex: 1;'><div style='flex: 1; text-align: left;'>Third Column</div><div><button type='button'><i class='fa fa-plus'></i></button></div></div>";
        },
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    }]
});


Keeping the last position on page refresh


<div id="millery-11" class="millery millery-theme-1"></div>
$("#millery-11").millery({
    panelType: "modal",
    keepState: true,
    stateSaveId: "millery-11-state",
    onnodeclick: function (instance, node, data) {
        instance.setPanelData("Node " + node.text() + " clicked!");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    }]
});


Using your own modal


<div id="millery-12" class="millery millery-theme-1"></div>
$("#millery-12").millery({
    panelType: null,
    onnodeclick: function (instance, node, data) {
        $("#myModal .modal-body").html("Node " + node.text() + " clicked!");
        $("#myModal").modal("show");
        return true;
    },
    columns: [{
        header: "First Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: "Second Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    },
    {
        header: "Third Column",
        sourceType: "json",
        source: "https://rettica.com/millery/docs/millery-data-single.json",
        idField: "id",
        parentIdField: "parent",
        childrenCountField: "children",
        labelField: "label"
    }]
});


Themes


Theme #1 (.millery-theme-1)


Theme #2 (.millery-theme-2)


Theme #3 (.millery-theme-3)


Theme #4 (.millery-theme-4)


Theme #5 (.millery-theme-5)



Changelog


v1.0.0

  • created first prototype
  • added back button
  • added panel types (overlay, always, modal)
  • added search capability
  • added column search
  • added local storage state saving
  • added keyboard navigation

v1.0.1

  • fixed CSS issues for IE11 and IE10

v1.0.2

-