/*
* 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":"")+"'> </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 = " "; } // 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 = {}; };
}));