You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1360 lines
51 KiB

6 years ago
  1. /*
  2. * http://github.com/deitch/jstree-grid
  3. *
  4. * This plugin handles adding a grid to a tree to display additional data
  5. *
  6. * Licensed under the MIT license:
  7. * http://www.opensource.org/licenses/mit-license.php
  8. *
  9. * Works only with jstree version >= 3.3.0
  10. *
  11. * $Date: 2017-04-19 $
  12. * $Revision: 3.8.2 $
  13. */
  14. /*jslint nomen:true */
  15. /*jshint unused:vars */
  16. /*global console, navigator, document, jQuery, define, localStorage */
  17. /* AMD support added by jochenberger per https://github.com/deitch/jstree-grid/pull/49
  18. *
  19. */
  20. (function (factory) {
  21. if (typeof define === 'function' && define.amd) {
  22. // AMD. Register as an anonymous module.
  23. define(['jquery', 'jstree'], factory);
  24. } else {
  25. // Browser globals
  26. factory(jQuery);
  27. }
  28. }(function ($) {
  29. var BLANKRE = /^\s*$/g,
  30. IDREGEX = /[\\:&!^|()\[\]<>@*'+~#";,= \/${}%]/g, escapeId = function (id) {
  31. return (id||"").replace(IDREGEX,'\\$&');
  32. }, NODE_DATA_ATTR = "data-jstreegrid", COL_DATA_ATTR = "data-jstreegrid-column",
  33. SEARCHCLASS = "jstree-search",
  34. SPECIAL_TITLE = "_DATA_", LEVELINDENT = 24, styled = false,
  35. MINCOLWIDTH = 10,
  36. generateCellId = function (tree,id) {
  37. return("jstree_"+tree+"_grid_"+escapeId(id)+"_col");
  38. },
  39. getIds = function (nodes) {
  40. return $.makeArray(nodes.map(function(){return this.id;}));
  41. },
  42. findDataCell = function (uniq, ids, col, scope) {
  43. if (scope == undefined) { scope = $(); };
  44. if (ids === null || ids === undefined || ids.length === 0) {
  45. return scope;
  46. }
  47. var ret = $(), columns = [].concat(col), cellId;
  48. if (typeof (ids) === "string") {
  49. cellId = generateCellId(uniq,ids);
  50. ret = columns.map(function (col) {
  51. return "#"+cellId+col;
  52. }).join(", ");
  53. } else {
  54. ret = []
  55. ids.forEach(function (elm,i) {
  56. var cellId = generateCellId(uniq,elm);
  57. ret = ret.concat(columns.map(function (col) {
  58. return "#"+cellId+col;
  59. }));
  60. });
  61. ret = ret.join(", ");
  62. }
  63. return columns.length ==1 ? scope.find(ret) : $(ret);
  64. },
  65. isClickedSep = false, toResize = null, oldMouseX = 0, newMouseX = 0,
  66. /*jslint regexp:true */
  67. htmlstripre = /<\/?[^>]+>/gi,
  68. /*jslint regexp:false */
  69. getIndent = function(node,tree) {
  70. var div, i, li, width;
  71. // did we already save it for this tree?
  72. tree._gridSettings = tree._gridSettings || {};
  73. if (tree._gridSettings.indent > 0) {
  74. width = tree._gridSettings.indent;
  75. } else {
  76. // create a new div on the DOM but not visible on the page
  77. div = $("<div></div>");
  78. i = node.prev("i");
  79. li = i.parent();
  80. // add to that div all of the classes on the tree root
  81. div.addClass(tree.get_node("#",true).attr("class"));
  82. // move the li to the temporary div root
  83. li.appendTo(div);
  84. // attach to the body quickly
  85. div.appendTo($("body"));
  86. // get the width
  87. width = i.width() || LEVELINDENT;
  88. // detach the li from the new div and destroy the new div
  89. li.detach();
  90. div.remove();
  91. // save it for the future
  92. tree._gridSettings.indent = width;
  93. }
  94. return(width);
  95. },
  96. copyData = function (fromtree,from,totree,to,recurse) {
  97. var i, j;
  98. to.data = $.extend(true, {}, from.data);
  99. if (from && from.children_d && recurse) {
  100. for(i = 0, j = from.children_d.length; i < j; i++) {
  101. copyData(fromtree,fromtree.get_node(from.children_d[i]),totree,totree.get_node(to.children_d[i]),recurse);
  102. }
  103. }
  104. },
  105. findLastClosedNode = function (tree,id) {
  106. // first get our node
  107. var ret, node = tree.get_node(id), children = node.children;
  108. // is it closed?
  109. if (!children || children.length <= 0 || !node.state.opened) {
  110. ret = id;
  111. } else {
  112. ret = findLastClosedNode(tree,children[children.length-1]);
  113. }
  114. return(ret);
  115. },
  116. renderAWidth = function(node,tree) {
  117. var depth, width,
  118. fullWidth = parseInt(tree.settings.grid.columns[0].width,10) + parseInt(tree._gridSettings.treeWidthDiff,10);
  119. // need to use a selector in jquery 1.4.4+
  120. depth = tree.get_node(node).parents.length;
  121. width = fullWidth - depth*getIndent(node,tree);
  122. // the following line is no longer needed, since we are doing this inside a <td>
  123. //a.css({"vertical-align": "top", "overflow":"hidden"});
  124. return(fullWidth);
  125. },
  126. renderATitle = function(node,t,tree) {
  127. var a = node.hasClass("jstree-anchor") ? node : node.children("[class~='jstree-anchor']"), title, col = tree.settings.grid.columns[0];
  128. // get the title
  129. title = "";
  130. if (col.title) {
  131. if (col.title === SPECIAL_TITLE) {
  132. title = tree.get_text(t);
  133. } else if (t.attr(col.title)) {
  134. title = t.attr(col.title);
  135. }
  136. }
  137. // strip out HTML
  138. title = title.replace(htmlstripre, '');
  139. if (title) {
  140. a.attr("title",title);
  141. }
  142. },
  143. getCellData = function (value,data) {
  144. var val;
  145. // get the contents of the cell - value could be a string or a function
  146. if (value !== undefined && value !== null) {
  147. if (typeof(value) === "function") {
  148. val = value(data);
  149. } else if (data.data !== null && data.data !== undefined && data.data[value] !== undefined) {
  150. val = data.data[value];
  151. } else {
  152. val = "";
  153. }
  154. } else {
  155. val = "";
  156. }
  157. return val;
  158. };
  159. $.jstree.defaults.grid = {
  160. width: 'auto'
  161. };
  162. $.jstree.plugins.grid = function(options,parent) {
  163. this._initialize = function () {
  164. if (!this._initialized) {
  165. var s = this.settings.grid || {}, styles, container = this.element, i,
  166. gs = this._gridSettings = {
  167. columns : s.columns || [],
  168. treeClass : "jstree-grid-col-0",
  169. context: s.contextmenu || false,
  170. columnWidth : s.columnWidth,
  171. defaultConf : {"*display":"inline","*+display":"inline"},
  172. isThemeroller : !!this._data.themeroller,
  173. treeWidthDiff : 0,
  174. resizable : s.resizable,
  175. draggable : s.draggable,
  176. stateful: s.stateful,
  177. indent: 0,
  178. sortOrder: 'text',
  179. sortAsc: true,
  180. caseInsensitive: s.caseInsensitive,
  181. fixedHeader: s.fixedHeader !== false,
  182. width: s.width,
  183. height: s.height,
  184. gridcontextmenu : s.gridcontextmenu,
  185. treecol: 0,
  186. gridcols: []
  187. }, cols = gs.columns, treecol = 0, columnSearch = false;
  188. if(gs.gridcontextmenu === true) {
  189. gs.gridcontextmenu = function (grid,tree,node,val,col,t,target) {
  190. return {
  191. "edit": {
  192. label: "Edit",
  193. "action": function (data) {
  194. var obj = t.get_node(node);
  195. grid._edit(obj,col,target);
  196. }
  197. }
  198. }
  199. }
  200. } else if (gs.gridcontextmenu === false) {
  201. gs.gridcontextmenu = false;
  202. }
  203. // find which column our tree shuld go in
  204. for (var i = 0, len = s.columns.length;i<len;i++) {
  205. if (s.columns[i].tree) {
  206. // save which column it was
  207. treecol = i;
  208. gs.treecol = treecol;
  209. } else {
  210. gs.gridcols.push(i);
  211. }
  212. }
  213. // set a unique ID for this table
  214. this.uniq = Math.ceil(Math.random()*1000);
  215. this.rootid = container.attr("id");
  216. var msie = /msie/.test(navigator.userAgent.toLowerCase());
  217. if (msie) {
  218. var version = parseFloat(navigator.appVersion.split("MSIE")[1]);
  219. if (version < 8) {
  220. gs.defaultConf.display = "inline";
  221. gs.defaultConf.zoom = "1";
  222. }
  223. }
  224. // set up the classes we need
  225. if (!styled) {
  226. styled = true;
  227. styles = [
  228. '.jstree-grid-cell {vertical-align: top; overflow:hidden;margin-left:0;position:relative;width: 100%;padding-left:7px;white-space: nowrap;}',
  229. '.jstree-grid-cell span {margin-right:0px;margin-right:0px;*display:inline;*+display:inline;white-space: nowrap;}',
  230. '.jstree-grid-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;}',
  231. '.jstree-grid-header-cell {overflow: hidden; white-space: nowrap;padding: 1px 3px 2px 5px; cursor: default;}',
  232. '.jstree-grid-header-themeroller {border: 0; padding: 1px 3px;}',
  233. '.jstree-grid-header-regular {position:relative; background-color: #EBF3FD; z-index: 1;}',
  234. '.jstree-grid-hidden {display: none;}',
  235. '.jstree-grid-resizable-separator {cursor: col-resize; width: 2px;}',
  236. '.jstree-grid-separator-regular {border-color: #d0d0d0; border-style: solid;}',
  237. '.jstree-grid-cell-themeroller {border: none !important; background: transparent !important;}',
  238. '.jstree-grid-wrapper {table-layout: fixed; width: 100%; overflow: auto; position: relative;}',
  239. '.jstree-grid-midwrapper {display: table-row;}',
  240. '.jstree-grid-width-auto {width:auto;display:block;}',
  241. '.jstree-grid-column {display: table-cell; overflow: hidden;}',
  242. '.jstree-grid-ellipsis {text-overflow: ellipsis;}',
  243. '.jstree-grid-col-0 {width: 100%;}'
  244. ];
  245. $('<style type="text/css">'+styles.join("\n")+'</style>').appendTo("head");
  246. }
  247. this.gridWrapper = $("<div></div>").addClass("jstree-grid-wrapper").insertAfter(container);
  248. this.midWrapper = $("<div></div>").addClass("jstree-grid-midwrapper").appendTo(this.gridWrapper);
  249. // set the wrapper width
  250. if (s.width) {
  251. this.gridWrapper.width(s.width);
  252. }
  253. if (s.height) {
  254. this.gridWrapper.height(s.height);
  255. }
  256. // create the data columns
  257. for (var i = 0, len = cols.length;i<len;i++) {
  258. // create the column
  259. $("<div></div>").addClass("jstree-default jstree-grid-column jstree-grid-column-"+i+" jstree-grid-column-root-"+this.rootid).appendTo(this.midWrapper);
  260. }
  261. this.midWrapper.children("div:eq("+treecol+")").append(container);
  262. container.addClass("jstree-grid-cell");
  263. //move header with scroll
  264. if (gs.fixedHeader) {
  265. this.gridWrapper.scroll(function() {
  266. $(this).find('.jstree-grid-header').css('top', $(this).scrollTop());
  267. });
  268. }
  269. // copy original sort function
  270. var defaultSort = $.proxy(this.settings.sort, this);
  271. // override sort function
  272. this.settings.sort = function (a, b) {
  273. var bigger, colrefs = this.colrefs;
  274. if (gs.sortOrder==='text') {
  275. var caseInsensitiveSort = this.get_text(a).toLowerCase().localeCompare(this.get_text(b).toLowerCase());
  276. bigger = gs.caseInsensitive ? (caseInsensitiveSort === 1) : (defaultSort(a, b) === 1);
  277. } else {
  278. // gs.sortOrder just refers to the unique random name for this column
  279. // we need to get the correct value
  280. var nodeA = this.get_node(a), nodeB = this.get_node(b),
  281. value = colrefs[gs.sortOrder].value,
  282. valueA = typeof(value) === 'function' ? value(nodeA) : nodeA.data[value],
  283. valueB = typeof(value) === 'function' ? value(nodeB) : nodeB.data[value];
  284. if(typeof(valueA) && typeof(valueB) !== 'undefined') {
  285. bigger = gs.caseInsensitive ? valueA.toLowerCase() > valueB.toLowerCase(): valueA > valueB ;
  286. }
  287. }
  288. if (!gs.sortAsc)
  289. bigger = !bigger;
  290. return bigger ? 1 : -1;
  291. };
  292. // sortable columns when jQuery UI is available
  293. if (gs.draggable) {
  294. if (!$.ui || !$.ui.sortable) {
  295. console.warn('[jstree-grid] draggable option requires jQuery UI');
  296. } else {
  297. var from, to;
  298. $(this.midWrapper).sortable({
  299. axis: "x",
  300. handle: ".jstree-grid-header",
  301. cancel: ".jstree-grid-separator",
  302. start: function (event, ui) {
  303. from = ui.item.index();
  304. },
  305. stop: function (event, ui) {
  306. to = ui.item.index();
  307. gs.columns.splice(to, 0, gs.columns.splice(from, 1)[0]);
  308. }
  309. });
  310. }
  311. }
  312. //public function. validate searchObject keys, set columnSearch flag, calls jstree search and reset columnSearch flag
  313. this.searchColumn = function (searchObj) {
  314. var validatedSearchObj = {};
  315. if(typeof searchObj == 'object') {
  316. for(var columnIndex in searchObj) {
  317. if(searchObj.hasOwnProperty(columnIndex)) {
  318. // keys should be the index of a column. This means the following:
  319. // only integers and smaller than the number of columns and bigger or equal to 0
  320. // (possilbe idea for in the future: ability to set key as a more human readable term like the column header and then map it here to an index)
  321. if (columnIndex % 1 === 0 && columnIndex < cols.length && columnIndex >= 0) {
  322. validatedSearchObj[columnIndex] = searchObj[columnIndex];
  323. }
  324. }
  325. }
  326. }
  327. columnSearch = validatedSearchObj;
  328. if(Object.keys(validatedSearchObj).length !== 0){
  329. //the search string doesn't matter. we'll use the search string in the columnSearch object!
  330. this.search('someValue');
  331. } else { // nothing to search so reset jstree's search by passing an empty string
  332. this.search('');
  333. }
  334. columnSearch = false;
  335. }
  336. // set default search for each column with no user defined search function (used when doing a columnSearch)
  337. for (var i = 0, len = cols.length; i<len; i++) {
  338. var column = cols[i];
  339. if (typeof(column.search_callback) !== "function") {
  340. // no search callback so set default function
  341. column.search_callback = function (str, columnValue, node, column) {
  342. var f = new $.vakata.search(str, true, { caseSensitive : searchSettings.case_sensitive, fuzzy : searchSettings.fuzzy });
  343. return f.search(columnValue).isMatch;
  344. };
  345. }
  346. }
  347. // if there was no overridden search_callback, we will provide it
  348. // it will use the default per-node search algorithm, augmented by searching our data nodes
  349. var searchSettings = this.settings.search;
  350. var omniSearchCallback = searchSettings.search_callback;
  351. if(!omniSearchCallback){
  352. omniSearchCallback = function (str,node) {
  353. var i, f = new $.vakata.search(str, true, { caseSensitive : searchSettings.case_sensitive, fuzzy : searchSettings.fuzzy }),
  354. matched = f.search(node.text).isMatch,
  355. col;
  356. // only bother looking in each cell if it was not yet matched
  357. if (!matched) {
  358. for (var i = 0, len = cols.length;i<len;i++) {
  359. if (treecol === i) {
  360. continue;
  361. }
  362. col = cols[i];
  363. matched = f.search(getCellData(col.value,node)).isMatch;
  364. if (matched) {
  365. break;
  366. }
  367. }
  368. }
  369. return matched;
  370. }
  371. }
  372. searchSettings.search_callback = function (str,node) {
  373. var matched = false;
  374. if(columnSearch){
  375. //using logical AND for column searches (more options in the future)
  376. for(var columnIndex in columnSearch) {
  377. if(columnSearch.hasOwnProperty(columnIndex)) {
  378. var searchValue = columnSearch[columnIndex];
  379. if(searchValue == ''){
  380. continue;
  381. }
  382. var col = cols[columnIndex];
  383. if(treecol == columnIndex){
  384. matched = col.search_callback(searchValue, node.text, node, col)
  385. } else {
  386. matched = col.search_callback(searchValue, getCellData(col.value,node), node, col)
  387. }
  388. if(!matched){
  389. break; //found one that didn't match
  390. }
  391. }
  392. }
  393. container.trigger("columnSearch_grid.jstree");
  394. } else {
  395. matched = omniSearchCallback(str, node);
  396. container.trigger("omniSearch_grid.jstree");
  397. }
  398. return matched;
  399. };
  400. this._initialized = true;
  401. }
  402. };
  403. this.init = function (el,options) {
  404. parent.init.call(this,el,options);
  405. this._initialize();
  406. };
  407. this.bind = function () {
  408. parent.bind.call(this);
  409. this._initialize();
  410. this.element
  411. .on("move_node.jstree create_node.jstree clean_node.jstree change_node.jstree", $.proxy(function (e, data) {
  412. var target = this.get_node(data || "#", true);
  413. var id = _guid();
  414. this._detachColumns(id);
  415. this._prepare_grid(target);
  416. this._reattachColumns(id);
  417. }, this))
  418. .on("delete_node.jstree",$.proxy(function (e,data) {
  419. if (data.node.id !== undefined) {
  420. var grid = this.gridWrapper, removeNodes = [data.node.id], i;
  421. // add children to remove list
  422. if (data.node && data.node.children_d) {
  423. removeNodes = removeNodes.concat(data.node.children_d);
  424. }
  425. findDataCell(this.uniq,removeNodes,this._gridSettings.gridcols).remove();
  426. }
  427. }, this))
  428. .on("show_node.jstree", $.proxy(function (e, data) {
  429. this._hideOrShowTree(data.node, false);
  430. }, this))
  431. .on("hide_node.jstree", $.proxy(function (e, data) {
  432. this._hideOrShowTree(data.node, true);
  433. }, this))
  434. .on("close_node.jstree",$.proxy(function (e,data) {
  435. this._hide_grid(data.node);
  436. }, this))
  437. .on("open_node.jstree",$.proxy(function (e,data) {
  438. }, this))
  439. .on("load_node.jstree",$.proxy(function (e,data) {
  440. }, this))
  441. .on("loaded.jstree", $.proxy(function (e) {
  442. this._prepare_headers();
  443. this.element.trigger("loaded_grid.jstree");
  444. }, this))
  445. .on("ready.jstree",$.proxy(function (e,data) {
  446. // find the line-height of the first known node
  447. var anchorHeight = this.element.find("[class~='jstree-anchor']:first").outerHeight(), q,
  448. cls = this.element.attr("class") || "";
  449. $('<style type="text/css">div.jstree-grid-cell-root-'+this.rootid+' {line-height: '+anchorHeight+'px; height: '+anchorHeight+'px;}</style>').appendTo("head");
  450. // add container classes to the wrapper - EXCEPT those that are added by jstree, i.e. "jstree" and "jstree-*"
  451. q = cls.split(/\s+/).map(function(i){
  452. var match = i.match(/^jstree(-|$)/);
  453. return (match ? "" : i);
  454. });
  455. this.gridWrapper.addClass(q.join(" "));
  456. },this))
  457. .on("move_node.jstree",$.proxy(function(e,data){
  458. var node = data.new_instance.element;
  459. //renderAWidth(node,this);
  460. // check all the children, because we could drag a tree over
  461. node.find("li > a").each($.proxy(function(i,elm){
  462. //renderAWidth($(elm),this);
  463. },this));
  464. },this))
  465. .on("hover_node.jstree",$.proxy(function(node,selected,event){
  466. var id = selected.node.id;
  467. if (this._hover_node !== null && this._hover_node !== undefined) {
  468. findDataCell(this.uniq,this._hover_node,this._gridSettings.gridcols).removeClass("jstree-hovered");
  469. }
  470. this._hover_node = id;
  471. findDataCell(this.uniq,id,this._gridSettings.gridcols).addClass("jstree-hovered");
  472. },this))
  473. .on("dehover_node.jstree",$.proxy(function(node,selected,event){
  474. var id = selected.node.id;
  475. this._hover_node = null;
  476. findDataCell(this.uniq,id,this._gridSettings.gridcols).removeClass("jstree-hovered");
  477. },this))
  478. .on("select_node.jstree",$.proxy(function(node,selected,event){
  479. var id = selected.node.id;
  480. findDataCell(this.uniq,id,this._gridSettings.gridcols).addClass("jstree-clicked");
  481. this.get_node(selected.node.id,true).children("div.jstree-grid-cell").addClass("jstree-clicked");
  482. },this))
  483. .on("deselect_node.jstree",$.proxy(function(node,selected,event){
  484. var id = selected.node.id;
  485. findDataCell(this.uniq,id,this._gridSettings.gridcols).removeClass("jstree-clicked");
  486. },this))
  487. .on("deselect_all.jstree",$.proxy(function(node,selected,event){
  488. // get all of the ids that were unselected
  489. var ids = selected.node || [], i;
  490. findDataCell(this.uniq,ids,this._gridSettings.gridcols).removeClass("jstree-clicked");
  491. },this))
  492. .on("search.jstree", $.proxy(function (e, data) {
  493. // search sometimes filters, so we need to hide all of the appropriate grid cells as well, and show only the matches
  494. var grid = this.gridWrapper, that = this, nodesToShow, startTime = new Date().getTime(),
  495. ids = getIds(data.nodes.filter(".jstree-node")), endTime;
  496. this.holdingCells = {};
  497. if (data.nodes.length) {
  498. var id = _guid();
  499. // save the cells we will hide
  500. var cells = grid.find('div.jstree-grid-cell-regular');
  501. this._detachColumns(id);
  502. if(this._data.search.som) {
  503. // create the list of nodes we want to look at
  504. if(this._data.search.smc) {
  505. nodesToShow = data.nodes.add(data.nodes.find('.jstree-node'));
  506. }
  507. nodesToShow = (nodesToShow || data.nodes).add(data.nodes.parentsUntil(".jstree"));
  508. // hide all of the grid cells
  509. cells.hide();
  510. // show only those that match
  511. nodesToShow.filter(".jstree-node").each(function (i,node) {
  512. var id = node.id;
  513. if (id) {
  514. that._prepare_grid(node);
  515. for (var i = 0, len = that._gridSettings.gridcols.length; i < len; i++) {
  516. if (i === that._gridSettings.treecol) { continue; }
  517. findDataCell(that.uniq, id, that._gridSettings.gridcols[i], $(that._domManipulation.columns[i])).show();
  518. }
  519. }
  520. });
  521. }
  522. for (var i = 0, len = this._gridSettings.gridcols.length; i < len; i++) {
  523. if (i === this._gridSettings.treecol) { continue; }
  524. findDataCell(that.uniq, ids, this._gridSettings.gridcols[i], $(this._domManipulation.columns[i])).addClass(SEARCHCLASS);
  525. }
  526. this._reattachColumns(id);
  527. endTime = new Date().getTime();
  528. this.element.trigger("search-complete.jstree-grid", [{time:endTime-startTime}]);
  529. }
  530. return true;
  531. }, this))
  532. .on("clear_search.jstree", $.proxy(function (e, data) {
  533. // search has been cleared, so we need to show all rows
  534. var grid = this.gridWrapper, ids = getIds(data.nodes.filter(".jstree-node"));
  535. grid.find('div.jstree-grid-cell').show();
  536. findDataCell(this.uniq,ids,this._gridSettings.gridcols).removeClass(SEARCHCLASS);
  537. return true;
  538. }, this))
  539. .on("copy_node.jstree", function (e, data) {
  540. var newtree = data.new_instance, oldtree = data.old_instance, obj = newtree.get_node(data.node,true);
  541. copyData(oldtree, data.original, newtree, data.node, true);
  542. newtree._detachColumns(obj.id);
  543. newtree._prepare_grid(obj);
  544. newtree._reattachColumns(obj.id);
  545. return true;
  546. })
  547. .on("show_ellipsis.jstree", $.proxy(function (e, data) {
  548. this.gridWrapper.find(".jstree-grid-cell").add(".jstree-grid-header", this.gridWrapper).addClass("jstree-grid-ellipsis");
  549. return true;
  550. }, this))
  551. .on("hide_ellipsis.jstree", $.proxy(function (e, data) {
  552. this.gridWrapper.find(".jstree-grid-cell").add(".jstree-grid-header", this.gridWrapper).removeClass("jstree-grid-ellipsis");
  553. return true;
  554. }, this))
  555. .on("enable_node.jstree", $.proxy(function (e, data) {
  556. var id = data.node.id;
  557. findDataCell(this.uniq,id,this._gridSettings.gridcols).removeClass("jstree-disabled");
  558. this.get_node(data.node.id,true).children("div.jstree-grid-cell").removeClass("jstree-disabled");
  559. }, this))
  560. .on("disable_node.jstree", $.proxy(function (e, data) {
  561. var id = data.node.id;
  562. findDataCell(this.uniq,id,this._gridSettings.gridcols).addClass("jstree-disabled");
  563. this.get_node(data.node.id,true).children("div.jstree-grid-cell").addClass("jstree-disabled");
  564. }, this))
  565. ;
  566. if (this._gridSettings.isThemeroller) {
  567. this.element
  568. .on("select_node.jstree",$.proxy(function(e,data){
  569. data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").addClass("ui-state-active");
  570. },this))
  571. .on("deselect_node.jstree deselect_all.jstree",$.proxy(function(e,data){
  572. data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").removeClass("ui-state-active");
  573. },this))
  574. .on("hover_node.jstree",$.proxy(function(e,data){
  575. data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").addClass("ui-state-hover");
  576. },this))
  577. .on("dehover_node.jstree",$.proxy(function(e,data){
  578. data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").removeClass("ui-state-hover");
  579. },this));
  580. }
  581. if (this._gridSettings.stateful) {
  582. this.element
  583. .on("resize_column.jstree-grid",$.proxy(function(e,col,width){
  584. localStorage['jstree-root-'+this.rootid+'-column-'+col] = width;
  585. },this));
  586. }
  587. };
  588. // tear down the tree entirely
  589. this.teardown = function() {
  590. var gw = this.gridWrapper, container = this.element, gridparent = gw.parent();
  591. container.detach();
  592. gw.remove();
  593. gridparent.append(container);
  594. parent.teardown.call(this);
  595. };
  596. // clean the grid in case of redraw or refresh entire tree
  597. this._clean_grid = function (target,id) {
  598. var grid = this.gridWrapper;
  599. if (target) {
  600. findDataCell(this.uniq,id,this._gridSettings.gridcols).remove();
  601. } else {
  602. // get all of the `div` children in all of the `td` in dataRow except for :first (that is the tree itself) and remove
  603. grid.find("div.jstree-grid-cell-regular").remove();
  604. }
  605. };
  606. // prepare the headers
  607. this._prepare_headers = function() {
  608. var header, i, col, _this = this, gs = this._gridSettings,cols = gs.columns || [], width, defaultWidth = gs.columnWidth, resizable = gs.resizable || false,
  609. cl, ccl, val, name, last, tr = gs.isThemeroller, classAdd = (tr?"themeroller":"regular"), puller,
  610. hasHeaders = false, gridparent = this.gridparent, rootid = this.rootid,
  611. conf = gs.defaultConf, coluuid,
  612. borPadWidth = 0, totalWidth = 0;
  613. // save the original parent so we can reparent on destroy
  614. this.parent = gridparent;
  615. // save the references to columns by unique ID
  616. this.colrefs = {};
  617. // create the headers
  618. for (var i = 0, len = cols.length;i<len;i++) {
  619. //col = $("<col/>");
  620. //col.appendTo(colgroup);
  621. cl = cols[i].headerClass || "";
  622. ccl = cols[i].columnClass || "";
  623. val = cols[i].header || "";
  624. do {
  625. coluuid = String(Math.floor(Math.random()*10000));
  626. } while(this.colrefs[coluuid] !== undefined);
  627. // create a unique name for this column
  628. name = cols[i].value ? coluuid : "text";
  629. this.colrefs[name] = cols[i];
  630. if (val) {hasHeaders = true;}
  631. if(gs.stateful && localStorage['jstree-root-'+rootid+'-column-'+i])
  632. width = localStorage['jstree-root-'+rootid+'-column-'+i];
  633. else
  634. width = cols[i].width || defaultWidth;
  635. var minWidth = cols[i].minWidth || width;
  636. var maxWidth = cols[i].maxWidth || width;
  637. // we only deal with borders if width is not auto and not percentages
  638. borPadWidth = tr ? 1+6 : 2+8; // account for the borders and padding
  639. if (width !== 'auto' && typeof(width) !== "string") {
  640. width -= borPadWidth;
  641. }
  642. col = this.midWrapper.children("div.jstree-grid-column-"+i);
  643. last = $("<div></div>").css(conf).addClass("jstree-grid-div-"+this.uniq+"-"+i+" "+(tr?"ui-widget-header ":"")+" jstree-grid-header jstree-grid-header-cell jstree-grid-header-"+classAdd+" "+cl+" "+ccl).html(val);
  644. last.addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-"+classAdd);
  645. if (this.settings.core.themes.ellipsis === true){
  646. last.addClass('jstree-grid-ellipsis');
  647. }
  648. last.prependTo(col);
  649. last.attr(COL_DATA_ATTR, name);
  650. totalWidth += last.outerWidth();
  651. puller = $("<div class='jstree-grid-separator jstree-grid-separator-"+classAdd+(tr ? " ui-widget-header" : "")+(resizable? " jstree-grid-resizable-separator":"")+"'>&nbsp;</div>").appendTo(last);
  652. col.width(width);
  653. col.css("min-width", minWidth);
  654. col.css("max-width", maxWidth);
  655. }
  656. last.addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-last jstree-grid-header-"+classAdd);
  657. // if there is no width given for the last column, do it via automatic
  658. if (cols[cols.length-1].width === undefined) {
  659. totalWidth -= width;
  660. col.css({width:"auto"});
  661. last.addClass("jstree-grid-width-auto").next(".jstree-grid-separator").remove();
  662. }
  663. if (hasHeaders) {
  664. // save the offset of the div from the body
  665. //gs.divOffset = header.parent().offset().left;
  666. gs.header = header;
  667. } else {
  668. $("div.jstree-grid-header").hide();
  669. }
  670. if (!this.bound && resizable) {
  671. this.bound = true;
  672. $(document).mouseup(function () {
  673. var ref, cols, width, headers, currentTree, colNum;
  674. if (isClickedSep) {
  675. colNum = toResize.prevAll(".jstree-grid-column").length;
  676. currentTree = toResize.closest(".jstree-grid-wrapper").find(".jstree");
  677. ref = $.jstree.reference(currentTree);
  678. cols = ref.settings.grid.columns;
  679. headers = toResize.parent().children("div.jstree-grid-column");
  680. if (isNaN(colNum) || colNum < 0) { ref._gridSettings.treeWidthDiff = currentTree.find("ins:eq(0)").width() + currentTree.find("[class~='jstree-anchor']:eq(0)").width() - ref._gridSettings.columns[0].width; }
  681. width = ref._gridSettings.columns[colNum].width = parseFloat(toResize.css("width"));
  682. isClickedSep = false;
  683. toResize = null;
  684. currentTree.trigger("resize_column.jstree-grid", [colNum,width]);
  685. }
  686. }).mousemove(function (e) {
  687. if (isClickedSep) {
  688. newMouseX = e.pageX;
  689. var diff = newMouseX - oldMouseX,
  690. oldPrevHeaderInner,
  691. oldPrevColWidth, newPrevColWidth;
  692. if (diff !== 0){
  693. oldPrevHeaderInner = toResize.width();
  694. oldPrevColWidth = parseFloat(toResize.css("width"));
  695. // handle a Chrome issue with columns set to auto
  696. // thanks to Brabus https://github.com/side-by-side
  697. if (!oldPrevColWidth) {oldPrevColWidth = toResize.innerWidth();}
  698. // make sure that diff cannot be beyond the left/right limits
  699. diff = diff < 0 ? Math.max(diff,-oldPrevHeaderInner) : diff;
  700. newPrevColWidth = oldPrevColWidth+diff;
  701. // only do this if we are not shrinking past 0 on left - and limit it to that amount
  702. if ((diff > 0 || oldPrevHeaderInner > 0) && newPrevColWidth > MINCOLWIDTH) {
  703. toResize.width(newPrevColWidth+"px");
  704. toResize.css("min-width",newPrevColWidth+"px");
  705. toResize.css("max-width",newPrevColWidth+"px");
  706. oldMouseX = newMouseX;
  707. }
  708. }
  709. }
  710. });
  711. this.gridWrapper.on("selectstart", ".jstree-grid-resizable-separator", function () {
  712. return false;
  713. }).on("mousedown", ".jstree-grid-resizable-separator", function (e) {
  714. isClickedSep = true;
  715. oldMouseX = e.pageX;
  716. toResize = $(this).closest("div.jstree-grid-column");
  717. // the max rightmost position we will allow is the right-most of the wrapper minus a buffer (10)
  718. return false;
  719. })
  720. .on("dblclick", ".jstree-grid-resizable-separator", function (e) {
  721. var clickedSep = $(this), col = clickedSep.closest("div.jstree-grid-column"),
  722. oldPrevColWidth = parseFloat(col.css("width")), newWidth = 0, diff,
  723. colNum = col.prevAll(".jstree-grid-column").length,
  724. oldPrevHeaderInner = col.width(), newPrevColWidth;
  725. //find largest width
  726. col.find(".jstree-grid-cell").each(function() {
  727. var item = $(this), width;
  728. item.css("position", "absolute");
  729. item.css("width", "auto");
  730. width = item.outerWidth();
  731. item.css("position", "relative");
  732. if (width>newWidth) {
  733. newWidth = width;
  734. }
  735. });
  736. diff = newWidth-oldPrevColWidth;
  737. // make sure that diff cannot be beyond the left limits
  738. diff = diff < 0 ? Math.max(diff,-oldPrevHeaderInner) : diff;
  739. newPrevColWidth = (oldPrevColWidth+diff)+"px";
  740. col.width(newPrevColWidth);
  741. col.css("min-width",newPrevColWidth);
  742. col.css("max-width",newPrevColWidth);
  743. $(this).closest(".jstree-grid-wrapper").find(".jstree").trigger("resize_column.jstree-grid",[colNum,newPrevColWidth]);
  744. })
  745. .on("click", ".jstree-grid-separator", function (e) {
  746. // don't sort after resize
  747. e.stopPropagation();
  748. });
  749. }
  750. this.gridWrapper.on("click", ".jstree-grid-header-cell", function (e) {
  751. if (!_this.sort) {
  752. return;
  753. }
  754. // get column
  755. var name = $(this).attr(COL_DATA_ATTR);
  756. // sort order
  757. var symbol;
  758. if (gs.sortOrder === name && gs.sortAsc === true) {
  759. gs.sortAsc = false;
  760. symbol = "&darr;";
  761. } else {
  762. gs.sortOrder = name;
  763. gs.sortAsc = true;
  764. symbol = "&uarr;";
  765. }
  766. // add sort arrow
  767. $(this.closest('.jstree-grid-wrapper')).find(".jstree-grid-sort-icon").remove();
  768. $("<span></span>").addClass("jstree-grid-sort-icon").appendTo($(this)).html(symbol);
  769. // sort by column
  770. var rootNode = _this.get_node('#');
  771. _this.sort(rootNode, true);
  772. _this.redraw_node(rootNode, true);
  773. });
  774. };
  775. this._domManipulation = null; // We'll store the column nodes in this object and an id for the grid-node that started the manipulation { id: "id of the node that started the manipulation", columns: { Key-Value-Pair col-No: Column }}
  776. function _guid() {
  777. function s4() {
  778. return Math.floor((1 + Math.random()) * 0x10000)
  779. .toString(16)
  780. .substring(1);
  781. }
  782. return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
  783. s4() + '-' + s4() + s4() + s4();
  784. }
  785. /*
  786. * Trys to detach the tree columns on massive dom manipulations
  787. */
  788. this._detachColumns = function (id) {
  789. // if the columns are not detached, then detach them
  790. if (this._domManipulation == null) {
  791. var cols = this._gridSettings.columns || [], treecol = this._gridSettings.treecol, mw = this.midWrapper;
  792. this._domManipulation = { id: id, columns: {} };
  793. for (var i = 0, len = cols.length; i < len; i++) {
  794. //if (treecol === i) {
  795. // continue;
  796. //}
  797. this._domManipulation.columns[i] = mw.children(".jstree-grid-column-" + i)[0];
  798. this._domManipulation.columns[i].parentNode.removeChild(this._domManipulation.columns[i]);
  799. }
  800. }
  801. return this._domManipulation;
  802. }
  803. this._reattachColumns = function (id) {
  804. if (this._domManipulation == null) { return false; }
  805. if (this._domManipulation.id === id) {
  806. var cols = this._gridSettings.columns || [], treecol = this._gridSettings.treecol, mw = this.midWrapper;
  807. for (var i = 0, len = cols.length; i < len; i++) {
  808. //if (treecol === i) {
  809. // continue;
  810. //}
  811. mw[0].appendChild(this._domManipulation.columns[i]);
  812. }
  813. this._domManipulation = null;
  814. }
  815. return true;
  816. }
  817. /*
  818. * Override open_node to detach the columns before redrawing child-nodes, and do reattach them afterwarts
  819. */
  820. this.open_node = function (obj, callback, animation) {
  821. var isArray = $.isArray(obj);
  822. var node = null;
  823. if (!isArray) {
  824. node = this.get_node(obj);
  825. if (node.id === "#") { return; } // wtf??? we ar in the root and do not need a open!
  826. }
  827. var id = isArray ? _guid() : node.id;
  828. this._detachColumns(id);
  829. var ret = parent.open_node.call(this, obj, callback, animation);
  830. this._reattachColumns(id);
  831. return ret;
  832. }
  833. /*
  834. * Override redraw_node to correctly insert the grid
  835. */
  836. this.redraw_node = function (obj, deep, is_callback, force_render) {
  837. var id = $.isArray(obj) ? _guid() : this.get_node(obj).id;
  838. // we detach the columns once
  839. this._detachColumns(id);
  840. // first allow the parent to redraw the node
  841. obj = parent.redraw_node.call(this, obj, deep, is_callback, force_render);
  842. // next prepare the grid for a redrawn node - but only if ths node is not hidden (search does that)
  843. if (obj) {
  844. this._prepare_grid(obj);
  845. }
  846. // don't forget to reattach
  847. this._reattachColumns(id);
  848. return obj;
  849. };
  850. this.refresh = function () {
  851. this._clean_grid();
  852. return parent.refresh.apply(this,arguments);
  853. };
  854. /*
  855. * Override set_id to update cell attributes
  856. */
  857. this.set_id = function (obj, id) {
  858. var old, uniq = this.uniq;
  859. if(obj) {
  860. old = obj.id;
  861. }
  862. var result = parent.set_id.apply(this,arguments);
  863. if(result) {
  864. if (old !== undefined) {
  865. var grid = this.gridWrapper, oldNodes = [old], i;
  866. // get children
  867. if (obj && obj.children_d) {
  868. oldNodes = oldNodes.concat(obj.children_d);
  869. }
  870. // update id in children
  871. findDataCell(uniq,oldNodes,this._gridSettings.gridcols)
  872. .attr(NODE_DATA_ATTR, obj.id)
  873. .removeClass(generateCellId(uniq,old))
  874. .addClass(generateCellId(uniq,obj.id))
  875. .each(function(i,node) {
  876. $(node).attr('id', generateCellId(uniq,obj.id)+(i+1));
  877. });
  878. }
  879. }
  880. return result;
  881. };
  882. this._hideOrShowTree = function(node, hide) {
  883. //Hides or shows a tree
  884. this._detachColumns(node.id);
  885. // show cells in each detachted column
  886. this._hideOrShowNode(node, hide, this._gridSettings.columns || [], this._gridSettings.treecol);
  887. this._reattachColumns(node.id);
  888. }
  889. this._hideOrShowNode = function(node, hide, cols, treecol) {
  890. //Hides or shows a node with recursive calls to all open child-nodes
  891. for (var i = 0, len = cols.length; i < len; i++) {
  892. if (i === treecol) { continue; }
  893. var cells = findDataCell(this.uniq, node.id, i, $(this._domManipulation.columns[i]));
  894. if (hide) {
  895. cells.addClass("jstree-grid-hidden");
  896. } else {
  897. cells.removeClass("jstree-grid-hidden");
  898. }
  899. }
  900. if (node.state.opened && node.children) {
  901. for (var i = 0, len = node.children.length; i < len; i++) {
  902. this._hideOrShowNode(this.get_node(node.children[i]), hide, cols, treecol);
  903. }
  904. }
  905. }
  906. this._hide_grid = function (node) {
  907. if (!node) { return true; }
  908. this._detachColumns(node.id);
  909. var children = node.children ? node.children : [], cols = this._gridSettings.columns || [], treecol = this._gridSettings.treecol;
  910. // try to remove all children
  911. for (var i = 0, len = children.length; i < len; i++) {
  912. var child = this.get_node(children[i]);
  913. // go through each column, remove all children with the correct ID name
  914. for (var j = 0, lenj = cols.length; j < lenj; j++) {
  915. if (j === treecol) { continue; }
  916. findDataCell(this.uniq, child.id, j, $(this._domManipulation.columns[j])).remove();
  917. }
  918. if (child.state.opened) { this._hide_grid(child);}
  919. }
  920. this._reattachColumns(node.id);
  921. };
  922. this.holdingCells = {};
  923. this.getHoldingCells = function (obj, col, hc) {
  924. if (obj.state.hidden || !obj.state.opened) { return $(); }
  925. var ret = $(), children = obj.children || [], child, i, uniq = this.uniq;
  926. // run through each child, render it, and then render its children recursively
  927. for (i = 0; i < children.length; i++) {
  928. child = generateCellId(uniq, children[i]) + col;
  929. if (hc[child]) {
  930. ret = ret.add(hc[child]).add(this.getHoldingCells(this.get_node(children[i]), col, hc));
  931. //delete hc[child];
  932. }
  933. }
  934. return (ret);
  935. };
  936. /**
  937. * put a grid cell in edit mode (input field to edit the data)
  938. * @name edit(obj, col)
  939. * @param {mixed} obj
  940. * @param {obj} col definition
  941. * @param {element} cell element, either span or wrapping div
  942. */
  943. this._edit = function (obj, col, element) {
  944. if(!obj) { return false; }
  945. if (!obj.data) {obj.data = {};}
  946. if (element) {
  947. element = $(element);
  948. if (element.prop("tagName").toLowerCase() === "div") {
  949. element = element.children("span:first");
  950. }
  951. } else {
  952. // need to find the element - later
  953. return false;
  954. }
  955. var rtl = this._data.core.rtl,
  956. w = this.element.width(),
  957. t = obj.data[col.value],
  958. h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
  959. h2 = $("<"+"input />", {
  960. "value" : t,
  961. "class" : "jstree-rename-input",
  962. "css" : {
  963. "padding" : "0",
  964. "border" : "1px solid silver",
  965. "box-sizing" : "border-box",
  966. "display" : "inline-block",
  967. "height" : (this._data.core.li_height) + "px",
  968. "lineHeight" : (this._data.core.li_height) + "px",
  969. "width" : "150px" // will be set a bit further down
  970. },
  971. "blur" : $.proxy(function () {
  972. var v = h2.val();
  973. // save the value if changed
  974. if(v === "" || v === t) {
  975. v = t;
  976. } else {
  977. obj.data[col.value] = v;
  978. this.element.trigger('update_cell.jstree-grid', { node: obj, col: col.value, value: v, old: t });
  979. var id = _guid();
  980. this._detachColumns(id);
  981. this._prepare_grid(this.get_node(obj, true));
  982. this._reattachColumns(id);
  983. }
  984. h2.remove();
  985. element.show();
  986. }, this),
  987. "keydown" : function (event) {
  988. var key = event.which;
  989. if(key === 27) {
  990. this.value = t;
  991. }
  992. if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
  993. event.stopImmediatePropagation();
  994. }
  995. if(key === 27 || key === 13) {
  996. event.preventDefault();
  997. this.blur();
  998. }
  999. },
  1000. "click" : function (e) { e.stopImmediatePropagation(); },
  1001. "mousedown" : function (e) { e.stopImmediatePropagation(); },
  1002. "keyup" : function (event) {
  1003. h2.width(Math.min(h1.text("pW" + this.value).width(),w));
  1004. },
  1005. "keypress" : function(event) {
  1006. if(event.which === 13) { return false; }
  1007. }
  1008. }),
  1009. fn = {
  1010. fontFamily : element.css('fontFamily') || '',
  1011. fontSize : element.css('fontSize') || '',
  1012. fontWeight : element.css('fontWeight') || '',
  1013. fontStyle : element.css('fontStyle') || '',
  1014. fontStretch : element.css('fontStretch') || '',
  1015. fontVariant : element.css('fontVariant') || '',
  1016. letterSpacing : element.css('letterSpacing') || '',
  1017. wordSpacing : element.css('wordSpacing') || ''
  1018. };
  1019. element.hide();
  1020. element.parent().append(h2);
  1021. h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
  1022. };
  1023. this.grid_hide_column = function (col) {
  1024. this.midWrapper.find(".jstree-grid-column-"+col).hide();
  1025. };
  1026. this.grid_show_column = function (col) {
  1027. this.midWrapper.find(".jstree-grid-column-"+col).show();
  1028. };
  1029. this._prepare_grid = function (obj) {
  1030. var gs = this._gridSettings, c = gs.treeClass, _this = this, t,
  1031. cols = gs.columns || [], width, tr = gs.isThemeroller, uniq = this.uniq,
  1032. treecol = gs.treecol,
  1033. tree = this.element, rootid = this.rootid,
  1034. classAdd = (tr?"themeroller":"regular"), img, objData = this.get_node(obj),
  1035. defaultWidth = gs.columnWidth, conf = gs.defaultConf, cellClickHandler = function (tree,node,val,col,t) {
  1036. return function(e) {
  1037. //node = tree.find("#"+node.attr("id"));
  1038. var event = jQuery.Event("select_cell.jstree-grid");
  1039. tree.trigger(event, [{value: val,column: col.header,node: node,grid:$(this),sourceName: col.value}]);
  1040. if (!event.isDefaultPrevented()) {
  1041. node.children(".jstree-anchor").trigger("click.jstree",e);
  1042. }
  1043. };
  1044. }, cellRightClickHandler = function (tree,node,val,col,t) {
  1045. return function (e) {
  1046. if (gs.gridcontextmenu) {
  1047. e.preventDefault();
  1048. $.vakata.context.show(this,{ 'x' : e.pageX, 'y' : e.pageY }, gs.gridcontextmenu(_this,tree,node,val,col,t,e.target));
  1049. }
  1050. };
  1051. },
  1052. hoverInHandler = function (node, jsTreeInstance) {
  1053. return function() { jsTreeInstance.hover_node(node); };
  1054. },
  1055. hoverOutHandler = function (node, jsTreeInstance) {
  1056. return function() { jsTreeInstance.dehover_node(node); };
  1057. },
  1058. i, val, cl, wcl, ccl, a, last, valClass, wideValClass, span, paddingleft, title, gridCellName, gridCellParentId, gridCellParent,
  1059. gridCellPrev, gridCellPrevId, gridCellNext, gridCellNextId, gridCellChild, gridCellChildId,
  1060. col, content, tmpWidth, mw = this.midWrapper, column, lid = objData.id,
  1061. highlightSearch, isClicked,
  1062. peers = this.get_node(objData.parent).children,
  1063. // find my position in the list of peers. "peers" is the list of everyone at my level under my parent, in order
  1064. pos = $.inArray(lid,peers),
  1065. hc = this.holdingCells, rendered = false, closed;
  1066. // get our column definition
  1067. t = $(obj);
  1068. // find the a children
  1069. a = t.children("[class~='jstree-anchor']");
  1070. highlightSearch = a.hasClass(SEARCHCLASS);
  1071. isClicked = a.hasClass("jstree-clicked");
  1072. if (a.length === 1) {
  1073. closed = !objData.state.opened;
  1074. gridCellName = generateCellId(uniq,lid);
  1075. gridCellParentId = objData.parent === "#" ? null : objData.parent;
  1076. a.addClass(c);
  1077. //renderAWidth(a,_this);
  1078. renderATitle(a,t,_this);
  1079. last = a;
  1080. // calculate position ids once
  1081. gridCellPrevId = pos <= 0 ? objData.parent : findLastClosedNode(this, peers[pos - 1]);
  1082. gridCellNextId = pos >= peers.length - 1 ? "NULL" : peers[pos + 1];
  1083. gridCellChildId = objData.children && objData.children.length > 0 ? objData.children[0] : "NULL";
  1084. // find which column our tree shuld go in
  1085. var s = this.settings.grid;
  1086. for (var i = 0, len = cols.length;i<len;i++) {
  1087. if (treecol === i) {
  1088. continue;
  1089. }
  1090. col = cols[i];
  1091. column = this._domManipulation == null ? mw.children("div:eq(" + i + ")") : $(this._domManipulation.columns[i]); //Geht the detached column not mw.children("div:eq("+i+")");
  1092. // get the cellClass, the wideCellClass, and the columnClass
  1093. cl = col.cellClass || "";
  1094. wcl = col.wideCellClass || "";
  1095. ccl = col.columnClass || "";
  1096. // add a column class to the Column
  1097. column.addClass(ccl);
  1098. // get the contents of the cell - value could be a string or a function
  1099. val = getCellData(col.value,objData);
  1100. if (typeof(col.format) === "function") {
  1101. val = col.format(val);
  1102. }
  1103. // put images instead of text if needed
  1104. if (col.images) {
  1105. img = col.images[val] || col.images["default"];
  1106. if (img) {content = img[0] === "*" ? '<span class="'+img.substr(1)+'"></span>' : '<img src="'+img+'">';}
  1107. } else { content = val; }
  1108. // content cannot be blank, or it messes up heights
  1109. if (content === undefined || content === null || BLANKRE.test(content)) {
  1110. content = "&nbsp;";
  1111. }
  1112. // get the valueClass
  1113. valClass = col.valueClass && objData.data !== null && objData.data !== undefined ? objData.data[col.valueClass] || "" : "";
  1114. if (valClass && col.valueClassPrefix && col.valueClassPrefix !== "") {
  1115. valClass = col.valueClassPrefix + valClass;
  1116. }
  1117. // get the wideValueClass
  1118. wideValClass = col.wideValueClass && objData.data !== null && objData.data !== undefined ? objData.data[col.wideValueClass] || "" : "";
  1119. if (wideValClass && col.wideValueClassPrefix && col.wideValueClassPrefix !== "") {
  1120. wideValClass = col.wideValueClassPrefix + wideValClass;
  1121. }
  1122. // get the title
  1123. title = col.title && objData.data !== null && objData.data !== undefined ? objData.data[col.title] || "" : "";
  1124. // strip out HTML
  1125. title = title.replace(htmlstripre, '');
  1126. // get the width
  1127. paddingleft = 7;
  1128. width = col.width || defaultWidth;
  1129. if (width !== 'auto') {
  1130. width = tmpWidth || (width - paddingleft);
  1131. }
  1132. last = findDataCell(uniq, lid, i, column);
  1133. if (!last || last.length < 1) {
  1134. last = $("<div></div>");
  1135. $("<span></span>").appendTo(last);
  1136. last.attr("id",gridCellName+i);
  1137. last.addClass(gridCellName);
  1138. last.attr(NODE_DATA_ATTR,lid);
  1139. if (highlightSearch) {
  1140. last.addClass(SEARCHCLASS);
  1141. } else {
  1142. last.removeClass(SEARCHCLASS);
  1143. }
  1144. if (isClicked) {
  1145. last.addClass("jstree-clicked");
  1146. } else {
  1147. last.removeClass("jstree-clicked");
  1148. }
  1149. if (this.settings.core.themes.ellipsis === true && i !== treecol) {
  1150. last.addClass('jstree-grid-ellipsis');
  1151. }
  1152. }
  1153. // we need to check the hidden-state and see if we need to hide the node
  1154. if (objData.state.hidden) {
  1155. last.addClass("jstree-grid-hidden");
  1156. } else {
  1157. last.removeClass("jstree-grid-hidden");
  1158. }
  1159. // ditto for the disabled-state and disabling the node
  1160. if (objData.state.disabled) {
  1161. last.addClass('jstree-disabled');
  1162. } else {
  1163. last.removeClass('jstree-disabled');
  1164. }
  1165. // we need to put it in the dataCell - after the parent, but the position matters
  1166. // if we have no parent, then we are one of the root nodes, but still need to look at peers
  1167. // if we are first, i.e. pos === 0, we go right after the parent;
  1168. // if we are not first, and our previous peer (one before us) is closed, we go right after the previous peer cell
  1169. // if we are not first, and our previous peer is opened, then we have to find its youngest & lowest closed child (incl. leaf)
  1170. //
  1171. // probably be much easier to go *before* our next one
  1172. // but that one might not be drawn yet
  1173. // here is the logic for jstree drawing:
  1174. // it draws peers from first to last or from last to first
  1175. // it draws children before a parent
  1176. //
  1177. // so I can rely on my *parent* not being drawn, but I cannot rely on my previous peer or my next peer being drawn
  1178. // so we do the following:
  1179. // 1- We are the first child: install after the parent
  1180. // 2- Our previous peer is already drawn: install after the previous peer
  1181. // 3- Our previous peer is not drawn, we have a child that is drawn: install right before our first child
  1182. // 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
  1183. // 5- Our previous peer is not drawn, we have no child that is drawn, our next peer is not drawn: install right after parent
  1184. gridCellPrev = findDataCell(uniq, gridCellPrevId, i, column);
  1185. gridCellNext = findDataCell(uniq, gridCellNextId, i, column);
  1186. gridCellChild = findDataCell(uniq, gridCellChildId, i, column);
  1187. gridCellParent = findDataCell(uniq, gridCellParentId, i, column);
  1188. // if our parent is already drawn, then we put this in the right order under our parent
  1189. if (gridCellParentId) {
  1190. if (gridCellParent && gridCellParent.length > 0) {
  1191. if (gridCellPrev && gridCellPrev.length > 0) {
  1192. last.insertAfter(gridCellPrev);
  1193. } else if (gridCellChild && gridCellChild.length > 0) {
  1194. last.insertBefore(gridCellChild);
  1195. } else if (gridCellNext && gridCellNext.length > 0) {
  1196. last.insertBefore(gridCellNext);
  1197. } else {
  1198. last.insertAfter(gridCellParent);
  1199. }
  1200. rendered = true;
  1201. } else {
  1202. rendered = false;
  1203. }
  1204. // always put it in the holding cells, and then sort when the parent comes in, in case parent is (re)drawn later
  1205. hc[gridCellName+i] = last;
  1206. } else {
  1207. if (gridCellPrev && gridCellPrev.length > 0) {
  1208. last.insertAfter(gridCellPrev);
  1209. } else if (gridCellChild && gridCellChild.length > 0) {
  1210. last.insertBefore(gridCellChild);
  1211. } else if (gridCellNext && gridCellNext.length > 0) {
  1212. last.insertBefore(gridCellNext);
  1213. } else {
  1214. last.appendTo(column);
  1215. }
  1216. rendered = true;
  1217. }
  1218. // do we have any children waiting for this cell? walk down through the children/grandchildren/etc tree
  1219. if (rendered) {
  1220. var toRen = this.getHoldingCells(objData,i,hc);
  1221. last.after(toRen);
  1222. }
  1223. // need to make the height of this match the line height of the tree. How?
  1224. span = last.children("span");
  1225. // create a span inside the div, so we can control what happens in the whole div versus inside just the text/background
  1226. span.addClass(cl+" "+valClass).html(content);
  1227. last = last.css(conf).addClass("jstree-grid-cell jstree-grid-cell-regular jstree-grid-cell-root-"+rootid+" jstree-grid-cell-"+classAdd+" "+wcl+ " " + wideValClass + (tr?" ui-state-default":"")).addClass("jstree-grid-col-"+i).addClass("jstree-animated");
  1228. // add click handler for clicking inside a grid cell
  1229. last.click(cellClickHandler(tree,t,val,col,this));
  1230. last.on("contextmenu",cellRightClickHandler(tree,t,val,col,this));
  1231. last.hover(hoverInHandler(t, this), hoverOutHandler(t, this));
  1232. if (title) {
  1233. span.attr("title",title);
  1234. }
  1235. tree.trigger("render_cell.jstree-grid", [{value: val, column: col.header, node: t, sourceName: col.value}]);
  1236. }
  1237. last.addClass("jstree-grid-cell-last"+(tr?" ui-state-default":""));
  1238. // if there is no width given for the last column, do it via automatic
  1239. if (cols[cols.length-1].width === undefined) {
  1240. last.addClass("jstree-grid-width-auto").next(".jstree-grid-separator").remove();
  1241. }
  1242. }
  1243. this.element.css({'overflow-y':'auto !important'});
  1244. };
  1245. // clean up holding cells
  1246. this.holdingCells = {};
  1247. // need to do alternating background colors or borders
  1248. };
  1249. }));