/*

* http://github.com/adamjimenez/jstree-table
*
* This plugin handles adding columns to a tree to display additional data
*
* Licensed under the MIT license:
*   http://www.opensource.org/licenses/mit-license.php
*
* Works only with jstree version >= 3.0.0
*
* $Revision:  3.4.2 $
*/

/*jslint nomen:true */ /*jshint unused:vars */ /*global navigator, document, jQuery, define, localStorage */

(function (factory) {

if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery', 'jstree'], factory);
} else {
        // Browser globals
        factory(jQuery);
}

}(function ($) {

var renderAWidth, renderATitle, getIndent, copyData, htmlstripre, findLastClosedNode, BLANKRE = /^\s*$/g,
        IDREGEX = /[\\:&!^|()\[\]<>@*'+~#";,= \/${}%]/g, escapeId = function (id) {
                return (id||"").replace(IDREGEX,'\\$&');
        }, NODE_DATA_ATTR = "data-jstreetable", COL_DATA_ATTR = "data-jstreetable-column",
SPECIAL_TITLE = "_DATA_", LEVELINDENT = 24, styled = false, TABLECELLID_PREFIX = "jstable_",TABLECELLID_POSTFIX = "_col",
        MINCOLWIDTH = 10,
        findDataCell = function (from,id) {
                return from.find("div["+NODE_DATA_ATTR+'="'+ escapeId(id) +'"]');
        },
        findDataCellFast = function(colNb,id) {
                return $(document.getElementById("jstable_"+id+"_col"+colNb));
        },
        isClickedSep = false, toResize = null, oldMouseX = 0, newMouseX = 0;

/*jslint regexp:true */
htmlstripre = /<\/?[^>]+>/gi;
/*jslint regexp:false */

getIndent = function(node,tree) {
        var div, i, li, width;

        // did we already save it for this tree?
        tree._tableSettings = tree._tableSettings || {};
        if (tree._tableSettings.indent > 0) {
                width = tree._tableSettings.indent;
        } else {
                // create a new div on the DOM but not visible on the page
                div = $("<div></div>");
                i = node.prev("i");
                li = i.parent();
                // add to that div all of the classes on the tree root
                div.addClass(tree.get_node("#",true).attr("class"));

                // move the li to the temporary div root
                li.appendTo(div);

                // attach to the body quickly
                div.appendTo($("body"));

                // get the width
                width = i.width() || LEVELINDENT;

                // detach the li from the new div and destroy the new div
                li.detach();
                div.remove();

                // save it for the future
                tree._tableSettings.indent = width;
        }

        return(width);

};

copyData = function (fromtree,from,totree,to,recurse) {
        var i, j;
  to.data = $.extend(true, {}, from.data);
        if (from && from.children_d && recurse) {
                for(i = 0, j = from.children_d.length; i < j; i++) {
                   copyData(fromtree,fromtree.get_node(from.children_d[i]),totree,totree.get_node(to.children_d[i]),recurse);
                }
        }
};

findLastClosedNode = function (tree,id) {
        // first get our node
        var ret, node = tree.get_node(id), children = node.children;
        // is it closed?
        if (!children || children.length <= 0 || !node.state.opened) {
                ret = id;
        } else {
                ret = findLastClosedNode(tree,children[children.length-1]);
        }
        return(ret);
};

renderAWidth = function(node,tree) {
        var depth, width,
        fullWidth = parseInt(tree.settings.table.columns[0].width,10) + parseInt(tree._tableSettings.treeWidthDiff,10);
        // need to use a selector in jquery 1.4.4+
        depth = tree.get_node(node).parents.length;
        width = fullWidth - depth*getIndent(node,tree);
        // the following line is no longer needed, since we are doing this inside a <td>
        //a.css({"vertical-align": "top", "overflow":"hidden"});
        return(fullWidth);
};
renderATitle = function(node,t,tree) {
        var a = node.get(0).tagName.toLowerCase() === "a" ? node : node.children("a"), title, col = tree.settings.table.columns[0];
        // get the title
        title = "";
        if (col.title) {
                if (col.title === SPECIAL_TITLE) {
                        title = tree.get_text(t);
                } else if (t.attr(col.title)) {
                        title = t.attr(col.title);
                }
        }
        // strip out HTML
        title = title.replace(htmlstripre, '');
        if (title) {
                a.attr("title",title);
        }
};

$.jstree.defaults.table = {
        width: 'auto'
};

$.jstree.plugins.table = function(options,parent) {
        var _this = this;

        this._initialize = function () {
                if (!this._initialized) {
                        var s = this.settings.table || {}, styles,      container = this.element, i,
                        gs = this._tableSettings = {
                                columns : s.columns || [],
                                treeClass : "jstree-table-col-0",
                                context: s.contextmenu || false,
                                columnWidth : s.columnWidth,
                                defaultConf : {"*display":"inline","*+display":"inline"},
                                isThemeroller : !!this._data.themeroller,
                                treeWidthDiff : 0,
                                resizable : s.resizable,
                                draggable : s.draggable,
                                stateful: s.stateful,
                                indent: 0,
                                sortFn: [],
                                sortOrder: 'text',
                                sortAsc: true,
                                fixedHeader: s.fixedHeader !== false,
                                headerContextMenu: s.headerContextMenu !== false,
                                checkIcon: 'fa fa-check',
                                arrowDownIcon: 'fa fa-chevron-down',
                                arrowUpIcon: 'fa fa-chevron-up',
                                width: s.width,
                                height: s.height
                        }, cols = gs.columns, treecol = 0;
                        // find which column our tree shuld go in
                        for (i=0;i<s.columns.length;i++) {
                                //Save sort function
                                if (i!==0 && s.columns[i].sort) {
                                        gs.sortFn[s.columns[i].value] = s.columns[i].sort;
                                }
                                if (s.columns[i].tree) {
                                        // save which column it was
                                        treecol = i;
                                        // do not check any others
                                        break;
                                }
                        }
                        // set a unique ID for this table
                        this.uniq = Math.ceil(Math.random()*1000);
                        this.rootid = container.attr("id");

                        var msie = /msie/.test(navigator.userAgent.toLowerCase());
                        if (msie) {
                                var version = parseFloat(navigator.appVersion.split("MSIE")[1]);
                                if (version < 8) {
                                        gs.defaultConf.display = "inline";
                                        gs.defaultConf.zoom = "1";
                                }
                        }

                        // set up the classes we need
                        if (!styled) {
                                styled = true;
                                styles = [
                                        '.jstree-table-cell {vertical-align: top; overflow:hidden;margin-left:0;width: 100%;padding-left:7px;white-space: nowrap; cursor: default; text-overflow: ellipsis;}',
                                        '.jstree-table-cell span {margin-right:0px;margin-right:0px;*display:inline;*+display:inline;white-space: nowrap;}',
                                        '.jstree-table-separator {position:absolute; top:0; right:0; height:24px; margin-left: -2px; border-width: 0 2px 0 0; *display:inline; *+display:inline; margin-right:0px;width:0px;}',
                                        '.jstree-table-header-cell {overflow: hidden; white-space: nowrap;padding: 4px 3px 2px 5px; cursor: default;}',
                                        '.jstree-table-header-themeroller {border: 0; padding: 1px 3px;}',
                                        '.jstree-table-header-regular {position:relative; background-color: #CBF3FD; z-index: 1;}',
                                        '.jstree-table-resizable-separator {cursor: col-resize; width: 10px;}',
                                        '.jstree-table-separator-regular {border-color: #d0d0d0; border-style: solid;}',
                                        '.jstree-table-cell-themeroller {border: none !important; background: transparent !important;}',
                                        '.jstree-table-wrapper {table-layout: fixed; width: 100%; overflow: auto; position: relative;}',
                                        '.jstree-table-midwrapper {display: table-row;}',
                                        '.jstree-table-width-auto {width:auto;display:block;}',
                                        '.jstree-table-column {display: table-cell; overflow: hidden;}',
                                        '.jstree-table-col-0 {width: calc(100% - 18px); overflow: hidden; text-overflow: ellipsis;}',
                                        '.jstree-table-sort-icon {font-size: 8px; position: absolute; top:0; left: calc(50% - 4px);}',
                                        '.jstree-table-midwrapper a.jstree-clicked, .jstree-table-midwrapper a.jstree-hovered{background: transparent; border-color: transparent;}',
                                        '.jstree-table-midwrapper a.jstree-clicked:before, .jstree-table-midwrapper a.jstree-hovered:before {position: absolute; left: 0; content:""; height: inherit; z-index: -1;}',
                                        '.jstree-table-midwrapper a.jstree-hovered:before {background: #e7f4f9;}',
                                        '.jstree-table-midwrapper a.jstree-clicked:before {background: #beebff;}',
                                        '.vakata-context {z-index:2;}'
                                ];

                                $('<style type="text/css">'+styles.join("\n")+'</style>').appendTo("head");
                        }
                        this.tableWrapper = $("<div></div>").addClass("jstree-table-wrapper").insertAfter(container);
                        this.midWrapper = $("<div></div>").addClass("jstree-table-midwrapper").appendTo(this.tableWrapper);
                        // set the wrapper width
                        if (s.width) {
                                this.tableWrapper.width(s.width);
                        }
                        if (s.height) {
                                this.tableWrapper.height(s.height);
                        }
                        // create the data columns
                        for (i=0;i<cols.length;i++) {
                                // create the column
                                $("<div></div>").addClass("jstree-default jstree-table-column jstree-table-column-"+i+" jstree-table-column-root-"+this.rootid).appendTo(this.midWrapper);

                                if (typeof(cols[i].value) === "function") {
                                        console.warn("[jstree-table] using value as a function is no longer supported, use 'format' option instead.");
                                }
                        }
                        this.midWrapper.children("div:eq("+treecol+")").append(container);
                        container.addClass("jstree-table-cell");

                        //move header with scroll
                        if (gs.fixedHeader) {
                                this.tableWrapper.scroll(function() {
                                        $(this).find('.jstree-table-header').css('top', $(this).scrollTop());
                                });
                        }

                        // copy original sort function
                        var defaultSort = $.proxy(this.settings.sort, this);

                        // override sort function
                        this.settings.sort = function (a, b) {
                                var bigger;

                                if (gs.sortOrder==='text') {
                                        bigger = defaultSort(a, b);
                                } else {
                                        var nodeA = this.get_node(a);
                                        var nodeB = this.get_node(b);
                                        var valueA = nodeA.data[gs.sortOrder];
                                        var valueB = nodeB.data[gs.sortOrder];
                                        if(valueA && valueB){
                                                if(gs.sortFn[gs.sortOrder]){
                                                        bigger = gs.sortFn[gs.sortOrder](valueA, valueB, nodeA, nodeB);
                                                }else{
                                                        // Default sorting
                                                        bigger = (valueA > valueB ? 1 : -1);
                                                }
                                        }else{
                                                // undefined is second
                                                if(valueA){
                                                        bigger = 1;
                                                }else if(valueB){
                                                        bigger = -1;
                                                }else{
                                                        // Compare two nodes without values
                                                        bigger = defaultSort(a, b);
                                                }
                                        }
                                }

                                if (gs.sortAsc===false){
                                        bigger = -bigger;

                                }

                                return bigger;
                        };

                        // sortable columns when jQuery UI is available
                        if (gs.draggable) {
                                if (!$.ui || !$.ui.sortable) {
                                        console.warn('[jstree-table] draggable option requires jQuery UI');
                                } else {
                                        var from, to;

                                        $(this.midWrapper).sortable({
                                                axis: "x",
                                                handle: ".jstree-table-header",
                                                cancel: ".jstree-table-separator",
                                                start: function (event, ui) {
                                                        from = ui.item.index();
                                                },
                                                stop: function (event, ui) {
                                                        to = ui.item.index();
                                                        gs.columns.splice(to, 0, gs.columns.splice(from, 1)[0]);
                                                }
                                        });
                                }
                        }

                        this._initialized = true;
                }
        };
        this.init = function (el,options) {
                parent.init.call(this,el,options);
                this._initialize();
        };
        this.bind = function () {
                parent.bind.call(this);
                this._initialize();
                this.element
                .on("move_node.jstree create_node.jstree clean_node.jstree change_node.jstree", $.proxy(function (e, data) {
                        var target = this.get_node(data || "#",true);
                        this._prepare_table(target);
                }, this))
                .on("delete_node.jstree",$.proxy(function (e,data) {
                        if (data.node.id !== undefined) {
                                var table = this.tableWrapper, removeNodes = [data.node.id], i;
                                // add children to remove list
                                if (data.node && data.node.children_d) {
                                        removeNodes = removeNodes.concat(data.node.children_d);
                                }
                                for (i=0;i<removeNodes.length;i++) {
                                        findDataCell(table,removeNodes[i]).remove();
                                }
                        }
                }, this))
                .on("close_node.jstree",$.proxy(function (e,data) {
                        this._hide_table(data.node);
                }, this))
                .on("open_node.jstree",$.proxy(function (e,data) {
                }, this))
                .on("load_node.jstree",$.proxy(function (e,data) {
                }, this))
                .on("loaded.jstree", $.proxy(function (e) {
                        this._prepare_headers();
                        this.element.trigger("loaded_table.jstree");
                        }, this))
                .on("ready.jstree",$.proxy(function (e,data) {
                        var cls = this.element.attr("class") || "";

                        // add container classes to the wrapper - EXCEPT those that are added by jstree, i.e. "jstree" and "jstree-*"
                        q = cls.split(/\s+/).map(function(i) {
                          var match = i.match(/^jstree(-|$)/);
                          return (match ? "" : i);
                        });
                        this.tableWrapper.addClass(q.join(" "));

                        var me = this;
                        function resize() {
                                // find the line-height of the first known node
                                var anchorHeight = me.element.find(".jstree-leaf").outerHeight();

                                if(anchorHeight === null) {
                                        var result = parent.create_node.call(me, '#',  { "id" : "tmp", "text" : "tmp" });
                                        anchorHeight = me.element.find(".jstree-leaf").outerHeight() || 24;
                                        parent.delete_node.call(me, "tmp");
                                }

                                // resize the hover/ focus highlight
                                var tableWidth = $('.jstree-table-midwrapper').width();

                                $('#jsTreeTableExtraCss').remove();
                                $('<style type="text/css" id="jsTreeTableExtraCss">\
                                        div.jstree-table-cell-root-'+me.rootid+' {line-height: '+anchorHeight+'px; min-height: '+anchorHeight+'px;}\
                                        div.jstree-table-midwrapper a.jstree-clicked:before, .jstree-table-midwrapper a.jstree-hovered:before {width: ' + tableWidth + 'px;}\
                                </style>').appendTo("head");
                        }

                        resize();

                        // resize rows on zoom
                        $(window).on('resize', resize);

                        // resize column expand
                        this.element.on("resize_column.jstree-table", resize);
                },this))
                .on("move_node.jstree",$.proxy(function(e,data) {
                        var node = data.new_instance.element;
                        //renderAWidth(node,this);
                        // check all the children, because we could drag a tree over
                        node.find("li > a").each($.proxy(function(i,elm) {
                                //renderAWidth($(elm),this);
                        },this));
                },this))
                .on("search.jstree", $.proxy(function (e, data) {
                        // search sometimes filters, so we need to hide all of the appropriate table cells as well, and show only the matches
                        var table = this.tableWrapper;
                        if(this._data.search.som) {
                                if(data.nodes.length) {
                                        // hide all of the table cells
                                        table.find('div.jstree-table-cell-regular').hide();
                                        // show only those that match
                                        data.nodes.add(data.nodes.parentsUntil(".jstree")).filter(".jstree-node").each(function (i,node) {
                                                var id = node.id;
                                                if (id) {
                                                        findDataCell(table,id).show();
                                                }
                                        });
                                }
                        }
                        return true;
                }, this))
                .on("clear_search.jstree", $.proxy(function (e, data) {
                        // search has been cleared, so we need to show all rows
                        this.tableWrapper.find('div.jstree-table-cell').show();
                        return true;
                }, this))
                .on("copy_node.jstree", function (e, data) {
                        var newtree = data.new_instance, oldtree = data.old_instance, obj = newtree.get_node(data.node,true);
                        copyData(oldtree,data.original,newtree,data.node,true);
                        newtree._prepare_table(obj);
                        return true;
                });
                if (this._tableSettings.isThemeroller) {
                        this.element
                                .on("select_node.jstree",$.proxy(function(e,data) {
                                        data.rslt.obj.children("a").nextAll("div").addClass("ui-state-active");
                                },this))
                                .on("deselect_node.jstree deselect_all.jstree",$.proxy(function(e,data) {
                                        data.rslt.obj.children("a").nextAll("div").removeClass("ui-state-active");
                                },this))
                                .on("hover_node.jstree",$.proxy(function(e,data) {
                                        data.rslt.obj.children("a").nextAll("div").addClass("ui-state-hover");
                                },this))
                                .on("dehover_node.jstree",$.proxy(function(e,data) {
                                        data.rslt.obj.children("a").nextAll("div").removeClass("ui-state-hover");
                                },this));
                }

                if (this._tableSettings.stateful) {
                        this.element
                                .on("resize_column.jstree-table",$.proxy(function(e,col,width) {
                                        localStorage['jstree-root-'+this.rootid+'-column-'+col] = width;
                                },this));
                }
        };
        // tear down the tree entirely
        this.teardown = function() {
                var gw = this.tableWrapper, container = this.element, tableparent = gw.parent();
                container.detach();
                gw.remove();
                tableparent.append(container);
                parent.teardown.call(this);
        };
        // clean the table in case of redraw or refresh entire tree
        this._clean_table = function (target,id) {
                var table = this.tableWrapper;
                if (target) {
                        findDataCell(table,id).remove();
                } else {
                        // get all of the `div` children in all of the `td` in dataRow except for :first (that is the tree itself) and remove
                        table.find("div.jstree-table-cell-regular").remove();
                }
        };
        // prepare the headers
        this._prepare_headers = function() {
                var header, i, col, _this = this, gs = this._tableSettings,cols = gs.columns || [], width, defaultWidth = gs.columnWidth, resizable = gs.resizable || false,
                cl, ccl, val, name, margin, last, tr = gs.isThemeroller, classAdd = (tr?"themeroller":"regular"), puller,
                hasHeaders = false, tableparent = this.tableparent, rootid = this.rootid,
                conf = gs.defaultConf,
                borPadWidth = 0, totalWidth = 0;
                // save the original parent so we can reparent on destroy
                this.parent = tableparent;

                // create the headers
                for (i=0;i<cols.length;i++) {
                        //col = $("<col/>");
                        //col.appendTo(colgroup);
                        cl = cols[i].headerClass || "";
                        ccl = cols[i].columnClass || "";
                        val = cols[i].header || "";
                        name = cols[i].value || "text";

                        if (val) {hasHeaders = true;}
                        if(gs.stateful && localStorage['jstree-root-'+rootid+'-column-'+i])
                                width = localStorage['jstree-root-'+rootid+'-column-'+i];
                        else
                                width = cols[i].width || defaultWidth;

                        col = this.midWrapper.children("div.jstree-table-column-"+i);
                        last = $("<div></div>").css(conf).addClass("jstree-table-div-"+this.uniq+"-"+i+" "+(tr?"ui-widget-header ":"")+" jstree-table-header jstree-table-header-cell jstree-table-header-"+classAdd+" "+cl+" "+ccl).html(val);
                        last.addClass((tr?"ui-widget-header ":"")+"jstree-table-header jstree-table-header-"+classAdd);
                        last.prependTo(col);

                        if (name) {
                                last.attr(COL_DATA_ATTR, name);
                        }
                        last.hover(function () {
                                $(this).addClass("jstree-hovered jstree-table-header-hovered");
                        }, function () {
                                $(this).removeClass("jstree-hovered jstree-table-header-hovered");
                        });
                        totalWidth += last.outerWidth();
                        puller = $("<div class='jstree-table-separator jstree-table-separator-"+classAdd+(tr ? " ui-widget-header" : "")+(resizable? " jstree-table-resizable-separator":"")+"'>&nbsp;</div>").appendTo(last);
                        col.width(width);
                        col.css("min-width",width);
                        col.css("max-width",width);
                }

                last.addClass((tr?"ui-widget-header ":"")+"jstree-table-header jstree-table-header-last jstree-table-header-"+classAdd);
                // if there is no width given for the last column, do it via automatic
                if (cols[cols.length-1].width === undefined) {
                        totalWidth -= width;
                        col.css({width:"auto"});
                        last.addClass("jstree-table-width-auto").next(".jstree-table-separator").remove();
                }
                if (hasHeaders) {
                        // save the offset of the div from the body
                        //gs.divOffset = header.parent().offset().left;
                        gs.header = header;
                } else {
                        $("div.jstree-table-header").hide();
                }

                if (!this.bound && resizable) {
                        this.bound = true;
                        $(document).mouseup(function () {
                                var ref, cols, width, headers, currentTree, colNum;
                                if (isClickedSep) {
                                        colNum = toResize.prevAll(".jstree-table-column").length;
                                        currentTree = toResize.closest(".jstree-table-wrapper").find(".jstree");
                                        ref = $.jstree.reference(currentTree);
                                        cols = ref.settings.table.columns;
                                        headers = toResize.parent().children("div.jstree-table-column");
                                        if (isNaN(colNum) || colNum < 0) { ref._tableSettings.treeWidthDiff = currentTree.find("ins:eq(0)").width() + currentTree.find("a:eq(0)").width() - ref._tableSettings.columns[0].width; }
                                        width = ref._tableSettings.columns[colNum].width = parseFloat(toResize.css("width"));
                                        isClickedSep = false;
                                        toResize = null;

                                        currentTree.trigger("resize_column.jstree-table", [colNum,width]);
                                }
                        }).mousemove(function (e) {
                                if (isClickedSep) {
                                        newMouseX = e.pageX;
                                        var diff = newMouseX - oldMouseX,
                                        oldPrevHeaderInner,
                                        oldPrevColWidth, newPrevColWidth;

                                        if (diff !== 0) {
                                                oldPrevHeaderInner = toResize.width();
                                                oldPrevColWidth = parseFloat(toResize.css("width"));

                                                // handle a Chrome issue with columns set to auto
                                                // thanks to Brabus https://github.com/side-by-side
                                                if (!oldPrevColWidth) {
                                                        oldPrevColWidth = toResize.innerWidth();
                                                }

                                                // make sure that diff cannot be beyond the left/right limits
                                                diff = diff < 0 ? Math.max(diff,-oldPrevHeaderInner) : diff;
                                                newPrevColWidth = oldPrevColWidth+diff;

                                                // only do this if we are not shrinking past 0 on left - and limit it to that amount
                                                if ((diff > 0 || oldPrevHeaderInner > 0) && newPrevColWidth > MINCOLWIDTH) {
                                                        toResize.width(newPrevColWidth+"px");
                                                        toResize.css("min-width",newPrevColWidth+"px");
                                                        toResize.css("max-width",newPrevColWidth+"px");
                                                        oldMouseX = newMouseX;
                                                }
                                        }
                                }
                        });
                        this.tableWrapper.on("selectstart", ".jstree-table-resizable-separator", function () {
                                return false;
                        })
                        .on("mousedown", ".jstree-table-resizable-separator", function (e) {
                                isClickedSep = true;
                                oldMouseX = e.pageX;
                                toResize = $(this).closest("div.jstree-table-column");
                                // the max rightmost position we will allow is the right-most of the wrapper minus a buffer (10)
                                return false;
                        })
                        .on("dblclick", ".jstree-table-resizable-separator", function (e) {
                                var col = $(this).closest("div.jstree-table-column");
                                _this.autosize_column(col);
                        })
                        .on("click", ".jstree-table-separator", function (e) {
                                // don't sort after resize
                                e.stopPropagation();
                        });
                }

                this.tableWrapper.on("click", ".jstree-table-header-cell", function (e) {
                        if (!_this.sort) { return; }

                        // get column
                        var name = $(this).attr(COL_DATA_ATTR);
                        if (!name) { return; }

                        // sort order
                        var arrowClass;
                        if (gs.sortOrder === name && gs.sortAsc === true) {
                                gs.sortAsc = false;
                                arrowClass = gs.arrowDownIcon;
                        } else {
                                gs.sortOrder = name;
                                gs.sortAsc = true;
                                arrowClass = gs.arrowUpIcon;
                        }

                        // add sort arrow
                        $(this).closest('.jstree-table-wrapper').find(".jstree-table-sort-icon").remove();
                        $("<span></span>").addClass("jstree-table-sort-icon").appendTo($(this)).addClass(arrowClass);

                        // sort by column
                        var rootNode = _this.get_node('#');
                        _this.sort(rootNode, true);
                        _this.redraw_node(rootNode, true);
                });

                // header context menu
                this.midWrapper.on("contextmenu", ".jstree-table-header-cell", function(e) {
                        if (!gs.headerContextMenu) { return; }
                                e.preventDefault();

                                var options = {
                                        "fit":{label:"Size column to fit","action": function (data) {
                                                var col = $(e.target).closest("div.jstree-table-column");
                                                _this.autosize_column(col);
                                        }},
                                        "fitAll":{"separator_after": true,label:"Size all columns to fit","action": function (data) {
                                                _this.autosize_all_columns();
                                        }}
                                };

                                // create menu item for every header cell
                                var cell, icon, value, label;
                                _this.midWrapper.find(".jstree-table-header-cell").each(function() {
                                        cell = $(this);
                                        icon = cell.is(":visible") ? gs.checkIcon : false;
                                        value = cell.attr(COL_DATA_ATTR);
                                        //get label without sorting arrows
                                        label = cell.clone().children('.jstree-table-sort-icon').remove().end().text().trim();

                                        options[value] = {icon:icon, column:value, label:label, _disabled: (value === 'text'), "action": function (data) {
                                                var col = _this.midWrapper.find(".jstree-table-header-cell["+COL_DATA_ATTR+"='"+data.item.column+"']").parent();
                                                col.toggle();
                                        }};
                                });

                                $.vakata.context.show(this,{ 'x' : e.pageX, 'y' : e.pageY },options);
                });
        };
        /*
         * Override redraw_node to correctly insert the table
         */
        this.redraw_node = function(obj, deep, is_callback, force_render) {
                // first allow the parent to redraw the node
                obj = parent.redraw_node.call(this, obj, deep, is_callback, force_render);
                // next prepare the table
                if(obj) {
                        this._prepare_table(obj);
                }
                return obj;
        };
        this.refresh = function () {
                this._clean_table();
                return parent.refresh.apply(this,arguments);
        };
        /*
         * Override set_id to update cell attributes
         */
        this.set_id = function (obj, id) {
                var old;
                if(obj) {
                        old = obj.id;
                }
                var result = parent.set_id.apply(this,arguments);
                if(result) {
                        if (old !== undefined) {
                                var table = this.tableWrapper, oldNodes = [old], i;
                                // get children
                                if (obj && obj.children_d) {
                                        oldNodes = oldNodes.concat(obj.children_d);
                                }
                                // update id in children
                                for (i=0;i<oldNodes.length;i++) {
                                        findDataCell(table,oldNodes[i])
                                        .attr(NODE_DATA_ATTR, obj.id)
                                        .attr('id', TABLECELLID_PREFIX+obj.id+TABLECELLID_POSTFIX+(i+1))
                                        .removeClass(TABLECELLID_PREFIX+old+TABLECELLID_POSTFIX)
                                        .addClass(TABLECELLID_PREFIX+obj.id+TABLECELLID_POSTFIX);
                                }
                        }
                }
                return result;
        };
        this._hide_table = function (node) {
                var children = node && node.children_d ? node.children_d : [], i;
                // go through each column, remove all children with the correct ID name
                for (i=0;i<children.length;i++) {
                        findDataCell(this.tableWrapper,children[i]).remove();
                }
        };
        this.holdingCells = {};
        this.getHoldingCells = function (obj,col,hc) {
                var ret = [];
                this._getHoldingCells(obj,col,hc,ret);
                return($(ret));
        };
        this._getHoldingCells = function (obj,col,hc,ret) {
                var children = obj.children||[], child, i;
                // run through each child, render it, and then render its children recursively
                for (i=0;i<children.length;i++) {
                        child = TABLECELLID_PREFIX+escapeId(children[i])+TABLECELLID_POSTFIX+col;
                        if (hc[child] && obj.state.opened) {
                                ret.push(hc[child][0]);
                                this._getHoldingCells(this.get_node(children[i]),col,hc,ret);
                                //delete hc[child];
                        }
                }
        };
        /**
         * put a table cell in edit mode (input field to edit the data)
         * @name edit(obj, col)
         * @param  {mixed} obj
         * @param  {obj} col definition
         * @param  {element} cell element, either span or wrapping div
         */
        this._edit = function (obj, col, element) {
                if(!obj) { return false; }
                if (element) {
                        element = $(element);
                        if (element.prop("tagName").toLowerCase() === "div") {
                                element = element.children("span:first");
                        }
                } else {
                        // need to find the element - later
                        return false;
                }
                var rtl = this._data.core.rtl,
                        w  = this.element.width(),
                        t = obj.data[col.value],
                        h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
                        h2 = $("<"+"input />", {
                                        "value" : t,
                                        "class" : "jstree-rename-input",
                                        "css" : {
                                                "padding" : "0",
                                                "border" : "1px solid silver",
                                                "box-sizing" : "border-box",
                                                "display" : "inline-block",
                                                "height" : (this._data.core.li_height) + "px",
                                                "lineHeight" : (this._data.core.li_height) + "px",
                                                "width" : "150px" // will be set a bit further down
                                        },
                                        "blur" : $.proxy(function () {
                                                var v = h2.val();
                                                // save the value if changed
                                                if(v === "" || v === t) {
                                                        v = t;
                                                } else {
                                                        obj.data[col.value] = v;
                                                        this.element.trigger('update_cell.jstree-table',{node:obj, col:col.value, value:v, old:t});
                                                        this._prepare_table(this.get_node(obj,true));
                                                }
                                                h2.remove();
                                                element.show();
                                        }, this),
                                        "keydown" : function (event) {
                                                var key = event.which;
                                                if(key === 27) {
                                                        this.value = t;
                                                }
                                                if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
                                                        event.stopImmediatePropagation();
                                                }
                                                if(key === 27 || key === 13) {
                                                        event.preventDefault();
                                                        this.blur();
                                                }
                                        },
                                        "click" : function (e) { e.stopImmediatePropagation(); },
                                        "mousedown" : function (e) { e.stopImmediatePropagation(); },
                                        "keyup" : function (event) {
                                                h2.width(Math.min(h1.text("pW" + this.value).width(),w));
                                        },
                                        "keypress" : function(event) {
                                                if(event.which === 13) { return false; }
                                        }
                                }),
                        fn = {
                                        fontFamily              : element.css('fontFamily')             || '',
                                        fontSize                : element.css('fontSize')                       || '',
                                        fontWeight              : element.css('fontWeight')             || '',
                                        fontStyle               : element.css('fontStyle')              || '',
                                        fontStretch             : element.css('fontStretch')            || '',
                                        fontVariant             : element.css('fontVariant')            || '',
                                        letterSpacing   : element.css('letterSpacing')  || '',
                                        wordSpacing             : element.css('wordSpacing')            || ''
                        };
                element.hide();
                element.parent().append(h2);
                h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
        };

        this.autosize_column = function (col) {
                // don't resize hidden columns
                if (col.is(":hidden")) { return; }

                var oldPrevColWidth = parseFloat(col.css("width")), newWidth = 0, diff,
                colNum = col.prevAll(".jstree-table-column").length,
                oldPrevHeaderInner = col.width(), newPrevColWidth;

                //find largest width
                col.find(".jstree-table-cell").each(function() {
                        var item = $(this), width;
                        item.css("position", "absolute");
                        item.css("width", "auto");
                        width = item.outerWidth();
                        item.css("position", "relative");

                        if (width>newWidth) {
                                newWidth = width;
                        }
                });

                diff = newWidth-oldPrevColWidth;

                // make sure that diff cannot be beyond the left limits
                diff = diff < 0 ? Math.max(diff,-oldPrevHeaderInner) : diff;
                newPrevColWidth = (oldPrevColWidth+diff)+"px";

                col.width(newPrevColWidth);
                col.css("min-width",newPrevColWidth);
                col.css("max-width",newPrevColWidth);

                $(this).closest(".jstree-table-wrapper").find(".jstree").trigger("resize_column.jstree-table",[colNum,newPrevColWidth]);
        };

        this.autosize_all_columns = function () {
                this.tableWrapper.find(".jstree-table-column").each(function() {
                        _this.autosize_column($(this));
                });
        };

        this._prepare_table = function (obj) {
                var gs = this._tableSettings, c = gs.treeClass, _this = this, t, cols = gs.columns || [], width, tr = gs.isThemeroller,
                tree = this.element, rootid = this.rootid,
                classAdd = (tr?"themeroller":"regular"), img, objData = this.get_node(obj),
                defaultWidth = gs.columnWidth, conf = gs.defaultConf, cellClickHandler = function (tree,node,val,col,t) {
                        return function(e) {
                                //node = tree.find("#"+node.attr("id"));
                                node.children(".jstree-anchor").trigger("click.jstree",e);
                                tree.trigger("select_cell.jstree-table", [{value: val,column: col.header,node: node,table:$(this),sourceName: col.value}]);
                        };
                }, cellRightClickHandler = function (tree,node,val,col,t) {
                        return function (e) {
                                if (gs.context) {
                                        e.preventDefault();
                                        $.vakata.context.show(this,{ 'x' : e.pageX, 'y' : e.pageY },{
                                                "edit":{label:"Edit","action": function (data) {
                                                        var obj = t.get_node(node);
                                                        _this._edit(obj,col,e.target);
                                                }}
                                        });
                                }
                        };
                },
                hoverInHandler = function (node, jsTreeInstance) {
                        return function() { jsTreeInstance.hover_node(node); };
                },
                hoverOutHandler = function (node, jsTreeInstance) {
                        return function() { jsTreeInstance.dehover_node(node); };
                },
                i, val, cl, wcl, ccl, a, last, valClass, wideValClass, span, paddingleft, title, tableCellName, tableCellParentId, tableCellParent,
                tableCellPrev, tableCellPrevId, tableCellNext, tableCellNextId, tableCellChild, tableCellChildId,
                col, content, tmpWidth, mw = this.midWrapper, dataCell, dataCellNb, lid = objData.id,
                peers = this.get_node(objData.parent).children,
                // find my position in the list of peers. "peers" is the list of everyone at my level under my parent, in order
                pos = $.inArray(lid,peers),
                hc = this.holdingCells, rendered = false, closed;
                // get our column definition
                t = $(obj);

                // find the a children
                a = t.children("a");

                if (a.length === 1) {
                        closed = !objData.state.opened;
                        tableCellName = TABLECELLID_PREFIX+escapeId(lid)+TABLECELLID_POSTFIX;
                        tableCellParentId = objData.parent === "#" ? null : objData.parent;
                        a.addClass(c);
                        //renderAWidth(a,_this);
                        renderATitle(a,t,_this);
                        last = a;
                        // find which column our tree shuld go in
                        var s = this.settings.table;
                        var treecol = 0;
                        for (i=0;i<s.columns.length;i++) {
                                if (s.columns[i].tree) {
                                        // save which column it was
                                        treecol = i;
                                        // do not check any others
                                        break;
                                }
                        }
                        for (i=0;i<cols.length;i++) {
                                if (treecol === i) {
                                        continue;
                                }
                                col = cols[i];
                                dataCell = mw.children("div:eq("+i+")");
                                dataCellNb = i;
                                // get the cellClass, the wideCellClass, and the columnClass
                                cl = col.cellClass || "";
                                wcl = col.wideCellClass || "";
                                ccl = col.columnClass || "";

                                // add a column class to the dataCell
                                dataCell.addClass(ccl);

                                // get the contents of the cell
                                val = "";
                                if (objData.data && objData.data[col.value] !== undefined) {
                                        val = objData.data[col.value];
                                }

                                if (typeof(col.format) === "function") {
                                        val = col.format(val);
                                }

                                // put images instead of text if needed
                                if (col.images) {
                                img = col.images[val] || col.images["default"];
                                if (img) {content = img[0] === "*" ? '<span class="'+img.substr(1)+'"></span>' : '<img src="'+img+'">';}
                                } else { content = val; }

                                // content cannot be blank, or it messes up heights
                                if (content === undefined || content === null || BLANKRE.test(content)) {
                                        content = "&nbsp;";
                                }

                                // get the valueClass
                                valClass = col.valueClass && objData.data !== null && objData.data !== undefined ? objData.data[col.valueClass] || "" : "";
                                if (valClass && col.valueClassPrefix && col.valueClassPrefix !== "") {
                                        valClass = col.valueClassPrefix + valClass;
                                }
                                // get the wideValueClass
                                wideValClass = col.wideValueClass && objData.data !== null && objData.data !== undefined ? objData.data[col.wideValueClass] || "" : "";
                                if (wideValClass && col.wideValueClassPrefix && col.wideValueClassPrefix !== "") {
                                        wideValClass = col.wideValueClassPrefix + wideValClass;
                                }
                                // get the title
                                title = col.title && objData.data !== null && objData.data !== undefined ? objData.data[col.title] || "" : "";
                                // strip out HTML
                                if (typeof title === 'string') {
                                        title = title.replace(htmlstripre, '');
                                }

                                // get the width
                                paddingleft = 7;
                                width = col.width || defaultWidth;
                                if (width !== 'auto') {
                                        width = tmpWidth || (width - paddingleft);
                                }

                                last = findDataCellFast(dataCellNb, lid);
                                if (!last || last.length < 1) {
                                        last = $("<div></div>");
                                        $("<span></span>").appendTo(last);
                                        last.attr("id",tableCellName+i);
                                        last.addClass(tableCellName);
                                        last.attr(NODE_DATA_ATTR,lid);

                                }
                                // we need to put it in the dataCell - after the parent, but the position matters
                                // if we have no parent, then we are one of the root nodes, but still need to look at peers

                                // if we are first, i.e. pos === 0, we go right after the parent;
                                // if we are not first, and our previous peer (one before us) is closed, we go right after the previous peer cell
                                // if we are not first, and our previous peer is opened, then we have to find its youngest & lowest closed child (incl. leaf)
                                //
                                // probably be much easier to go *before* our next one
                                // but that one might not be drawn yet
                                // here is the logic for jstree drawing:
                                //   it draws peers from first to last or from last to first
                                //   it draws children before a parent
                                //
                                // so I can rely on my *parent* not being drawn, but I cannot rely on my previous peer or my next peer being drawn

                                // so we do the following:
                                //   1- We are the first child: install after the parent
                                //   2- Our previous peer is already drawn: install after the previous peer
                                //   3- Our previous peer is not drawn, we have a child that is drawn: install right before our first child
                                //   4- Our previous peer is not drawn, we have no child that is drawn, our next peer is drawn: install right before our next peer
                                //   5- Our previous peer is not drawn, we have no child that is drawn, our next peer is not drawn: install right after parent
                                tableCellPrevId = pos <=0 ? objData.parent : findLastClosedNode(this,peers[pos-1]);
                                tableCellPrev = findDataCellFast(dataCellNb,tableCellPrevId);
                                tableCellNextId = pos >= peers.length-1 ? "NULL" : peers[pos+1];
                                tableCellNext = findDataCellFast(dataCellNb,tableCellNextId);
                                tableCellChildId = objData.children && objData.children.length > 0 ? objData.children[0] : "NULL";
                                tableCellChild = findDataCellFast(dataCellNb,tableCellChildId);
                                tableCellParent = findDataCellFast(dataCellNb,tableCellParentId);

                                // if our parent is already drawn, then we put this in the right order under our parent
                                if (tableCellParentId) {
                                        if (tableCellParent && tableCellParent.length > 0) {
                                                if (tableCellPrev && tableCellPrev.length > 0) {
                                                        last.insertAfter(tableCellPrev);
                                                } else if (tableCellChild && tableCellChild.length > 0) {
                                                        last.insertBefore(tableCellChild);
                                                } else if (tableCellNext && tableCellNext.length > 0) {
                                                        last.insertBefore(tableCellNext);
                                                } else {
                                                        last.insertAfter(tableCellParent);
                                                }
                                                rendered = true;
                                        } else {
                                                rendered = false;
                                        }
                                        // always put it in the holding cells, and then sort when the parent comes in, in case parent is (re)drawn later
                                        hc[tableCellName+i] = last;
                                } else {
                                        if (tableCellPrev && tableCellPrev.length > 0) {
                                                last.insertAfter(tableCellPrev);
                                        } else if (tableCellChild && tableCellChild.length > 0) {
                                                last.insertBefore(tableCellChild);
                                        } else if (tableCellNext && tableCellNext.length > 0) {
                                                last.insertBefore(tableCellNext);
                                        } else {
                                                last.appendTo(dataCell);
                                        }
                                        rendered = true;
                                }
                                // do we have any children waiting for this cell? walk down through the children/grandchildren/etc tree
                                if (rendered) {
                                        last.after(this.getHoldingCells(objData,i,hc));
                                }
                                // need to make the height of this match the line height of the tree. How?
                                span = last.children("span");

                                // create a span inside the div, so we can control what happens in the whole div versus inside just the text/background
                                span.addClass(cl+" "+valClass).html(content);
                                last = last.css(conf).addClass("jstree-table-cell jstree-table-cell-regular jstree-table-cell-root-"+rootid+" jstree-table-cell-"+classAdd+" "+wcl+ " " + wideValClass + (tr?" ui-state-default":"")).addClass("jstree-table-col-"+i);
                                // add click handler for clicking inside a table cell
                                last.click(cellClickHandler(tree,t,val,col,this));
                                last.on("contextmenu",cellRightClickHandler(tree,t,val,col,this));
                                last.hover(hoverInHandler(t, this), hoverOutHandler(t, this));

                                if (title) {
                                        span.attr("title",title);
                                }
                        }
                        last.addClass("jstree-table-cell-last"+(tr?" ui-state-default":""));
                        // if there is no width given for the last column, do it via automatic
                        if (cols[cols.length-1].width === undefined) {
                                last.addClass("jstree-table-width-auto").next(".jstree-table-separator").remove();
                        }
                }
                this.element.css({'overflow-y':'auto !important'});
        };
        // clean up holding cells
        this.holdingCells = {};
};

}));