/jstreegrid.js

https://github.com/gigo6000/jstree-grid · JavaScript · 292 lines · 222 code · 27 blank · 43 comment · 58 complexity · b29f808c95ececabc5531bf6da08e019 MD5 · raw file

  1. /*
  2. * jsTreeGrid 0.98
  3. * http://jsorm.com/
  4. *
  5. * This plugin handles adding a grid to a tree to display additional data
  6. *
  7. * Dual licensed under the MIT and GPL licenses (same as jQuery):
  8. * http://www.opensource.org/licenses/mit-license.php
  9. * http://www.gnu.org/licenses/gpl.html
  10. *
  11. * Created for Tufin www.tufin.com
  12. * Contributed to public source through the good offices of Tufin
  13. *
  14. * $Date: 2010-10-28 $
  15. * $Revision: $
  16. */
  17. /*jslint nomen:false */
  18. /*global window, document, jQuery*/
  19. (function ($) {
  20. var renderAWidth, renderATitle, htmlstripre, SPECIAL_TITLE = "_DATA_", bound = false, styled = false;
  21. /*jslint regexp:false */
  22. htmlstripre = /<\/?[^>]+>/gi;
  23. /*jslint regexp:true */
  24. renderAWidth = function(node,tree) {
  25. var depth, a = node.get(0).tagName.toLowerCase() === "a" ? node : node.children("a"),
  26. width = parseInt(tree.data.grid.columns[0].width) + parseInt(tree.data.grid.treeWidthDiff);
  27. // need to use a selector in jquery 1.4.4+
  28. depth = a.parentsUntil(tree.get_container().get(0).tagName+".jstree").filter("li").length;
  29. width = width - depth*18;
  30. a.css({width: width, "vertical-align": "top", "overflow":"hidden","float":"left"});
  31. };
  32. renderATitle = function(node,t,tree) {
  33. var a = node.get(0).tagName.toLowerCase() === "a" ? node : node.children("a"), title, col = tree.data.grid.columns[0];
  34. // get the title
  35. title = "";
  36. if (col.title) {
  37. if (col.title === SPECIAL_TITLE) {
  38. title = tree.get_text(t);
  39. } else if (t.attr(col.title)) {
  40. title = t.attr(col.title);
  41. }
  42. }
  43. // strip out HTML
  44. title = title.replace(htmlstripre, '');
  45. if (title) {
  46. a.attr("title",title);
  47. }
  48. };
  49. $.jstree.plugin("grid", {
  50. __init : function () {
  51. var s = this._get_settings().grid || {}, styles;
  52. this.data.grid.columns = s.columns || [];
  53. this.data.grid.treeClass = "jstree-grid-col-0";
  54. this.data.grid.columnWidth = s.width;
  55. this.data.grid.defaultConf = {display: "inline-block","*display":"inline","*+display":"inline","float":"left"};
  56. this.data.grid.isThemeroller = !!this.data.themeroller;
  57. this.data.grid.treeWidthDiff = 0;
  58. this.data.grid.resizable = s.resizable;
  59. if ($.browser.msie && parseInt($.browser.version.substr(0,1),10) < 8) {
  60. this.data.grid.defaultConf.display = "inline";
  61. this.data.grid.defaultConf.zoom = "1";
  62. }
  63. // set up the classes we need
  64. if (!styled) {
  65. styled = true;
  66. styles = [
  67. '.jstree-grid-cell {padding-left: 4px; vertical-align: top; overflow:hidden;}',
  68. '.jstree-grid-separator {display: inline-block; border-width: 0 2px 0 0; *display:inline; *+display:inline; margin-right:0px;float:left;}',
  69. '.jstree-grid-header-cell {float: left;}',
  70. '.jstree-grid-header-themeroller {border: 0; padding: 1px 3px;}',
  71. '.jstree-grid-header-regular {background-color: #EBF3FD;}',
  72. '.jstree-grid-resizable-separator {cursor: col-resize;}',
  73. '.jstree-grid-separator-regular {border-color: #d0d0d0; border-style: solid;}',
  74. '.jstree-grid-cell-themeroller {border: none !important; background: transparent !important;}'
  75. ];
  76. $('<style type="text/css">'+styles.join("\n")+'</style>').appendTo("head");
  77. }
  78. this.get_container().bind("open_node.jstree create_node.jstree clean_node.jstree change_node.jstree", $.proxy(function (e, data) {
  79. var target = data && data.rslt && data.rslt.obj ? data.rslt.obj : e.target;
  80. target = $(target);
  81. this._prepare_grid(target);
  82. }, this))
  83. .bind("loaded.jstree", $.proxy(function (e) {
  84. this._prepare_headers();
  85. this._prepare_grid();
  86. this.get_container().trigger("loaded_grid.jstree");
  87. }, this))
  88. .bind("move_node.jstree",$.proxy(function(e,data){
  89. var node = data.rslt.o;
  90. renderAWidth(node,this);
  91. // check all the children, because we could drag a tree over
  92. node.find("li > a").each($.proxy(function(i,elm){
  93. renderAWidth($(elm),this);
  94. },this));
  95. },this));
  96. if (this.data.grid.isThemeroller) {
  97. this.get_container()
  98. .bind("select_node.jstree",$.proxy(function(e,data){
  99. data.rslt.obj.children("a").nextAll("div").addClass("ui-state-active");
  100. },this))
  101. .bind("deselect_node.jstree deselect_all.jstree",$.proxy(function(e,data){
  102. data.rslt.obj.children("a").nextAll("div").removeClass("ui-state-active");
  103. },this))
  104. .bind("hover_node.jstree",$.proxy(function(e,data){
  105. data.rslt.obj.children("a").nextAll("div").addClass("ui-state-hover");
  106. },this))
  107. .bind("dehover_node.jstree",$.proxy(function(e,data){
  108. data.rslt.obj.children("a").nextAll("div").removeClass("ui-state-hover");
  109. },this));
  110. }
  111. },
  112. __destroy : function() {
  113. var parent = this.data.grid.parent, container = this.get_container();
  114. container.detach();
  115. $("div.jstree-grid-wrapper",parent).remove();
  116. parent.append(container);
  117. },
  118. defaults : {
  119. width: 25
  120. },
  121. _fn : {
  122. _prepare_headers : function() {
  123. var header, i, cols = this.data.grid.columns || [], width, defaultWidth = this.data.grid.columnWidth, resizable = this.data.grid.resizable || false,
  124. cl, val, margin, last, tr = this.data.grid.isThemeroller, classAdd = (tr?"themeroller":"regular"),
  125. cHeight, hHeight, container = this.get_container(), parent = container.parent(), hasHeaders = 0,
  126. conf = this.data.grid.defaultConf, isClickedSep = false, oldMouseX = 0, newMouseX = 0, currentTree = null, colNum = 0, toResize = null, clickedSep = null, borPadWidth = 0;
  127. // save the original parent so we can reparent on destroy
  128. this.data.grid.parent = parent;
  129. // set up the wrapper, if not already done
  130. header = this.data.grid.header || $("<div></div>").addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-"+classAdd);
  131. // create the headers
  132. for (i=0;i<cols.length;i++) {
  133. cl = cols[i].headerClass || "";
  134. val = cols[i].header || "";
  135. if (val) {hasHeaders = true;}
  136. width = cols[i].width || defaultWidth;
  137. borPadWidth = tr ? 1+6 : 2+8; // account for the borders and padding
  138. width -= borPadWidth;
  139. margin = i === 0 ? 3 : 0;
  140. last = $("<div></div>").css(conf).css({"margin-left": margin,"width":width, "padding": "1 3 2 5"}).addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-cell jstree-grid-header-"+classAdd+" "+cl).text(val).appendTo(header)
  141. .after("<div class='jstree-grid-separator jstree-grid-separator-"+classAdd+(tr ? " ui-widget-header" : "")+(resizable? " jstree-grid-resizable-separator":"")+"'>&nbsp;</div>");
  142. }
  143. last.addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-"+classAdd);
  144. // add a clearer
  145. $("<div></div>").css("clear","both").appendTo(header);
  146. // did we have any real columns?
  147. if (hasHeaders) {
  148. $("<div></div>").addClass("jstree-grid-wrapper").appendTo(parent).append(header).append(container);
  149. // save the offset of the div from the body
  150. this.data.grid.divOffset = header.parent().offset().left;
  151. this.data.grid.header = header;
  152. }
  153. if (!bound && resizable) {
  154. bound = true;
  155. $(".jstree-grid-separator")
  156. .live("selectstart", function () { return false; })
  157. .live("mousedown", function (e) {
  158. clickedSep = $(this);
  159. isClickedSep = true;
  160. currentTree = clickedSep.parents(".jstree-grid-wrapper").children(".jstree");
  161. oldMouseX = e.clientX;
  162. colNum = clickedSep.prevAll(".jstree-grid-header").length-1;
  163. toResize = clickedSep.prev().add(currentTree.find(".jstree-grid-col-"+colNum));
  164. return false;
  165. });
  166. $(document)
  167. .mouseup(function () {
  168. var i, ref, cols, widths, headers;
  169. if (isClickedSep) {
  170. ref = $.jstree._reference(currentTree);
  171. cols = ref.data.grid.columns;
  172. headers = clickedSep.parent().children(".jstree-grid-header");
  173. widths = {};
  174. if (!colNum) { ref.data.grid.treeWidthDiff = currentTree.find("ins:eq(0)").width() + currentTree.find("a:eq(0)").width() - ref.data.grid.columns[0].width; }
  175. isClickedSep = false;
  176. for (i=0;i<cols.length;i++) { widths[cols[i].header] = {w: parseFloat(headers[i].style.width)+borPadWidth, r: i===colNum }; }
  177. currentTree.trigger("resize_column.jstree-grid", [widths]);
  178. }
  179. })
  180. .mousemove(function (e) {
  181. if (isClickedSep) {
  182. newMouseX = e.clientX;
  183. var diff = newMouseX - oldMouseX;
  184. toResize.each(function () { this.style.width = parseFloat(this.style.width) + diff + "px"; });
  185. oldMouseX = newMouseX;
  186. }
  187. });
  188. }
  189. },
  190. _prepare_grid : function(obj) {
  191. var c = this.data.grid.treeClass, _this = this, t, cols = this.data.grid.columns || [], width, tr = this.data.grid.isThemeroller,
  192. classAdd = (tr?"themeroller":"regular"), img,
  193. defaultWidth = this.data.grid.columnWidth, divOffset = this.data.grid.divOffset, conf = this.data.grid.defaultConf;
  194. obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
  195. // get our column definition
  196. obj.each(function () {
  197. var i, val, cl, wcl, a, last, valClass, wideValClass, span, paddingleft, title, isAlreadyGrid, col, content, s, tmpWidth;
  198. t = $(this);
  199. // find the a children
  200. a = t.children("a");
  201. isAlreadyGrid = a.hasClass(c);
  202. if (a.length === 1) {
  203. a.prev().css("float","left");
  204. a.addClass(c);
  205. renderAWidth(a,_this);
  206. renderATitle(a,t,_this);
  207. last = a;
  208. for (i=1;i<cols.length;i++) {
  209. col = cols[i];
  210. s = col.source || "attr";
  211. // get the cellClass and the wideCellClass
  212. cl = col.cellClass || "";
  213. wcl = col.wideCellClass || "";
  214. // get the contents of the cell
  215. if (s === "attr") { val = col.value && t.attr(col.value) ? t.attr(col.value) : "";
  216. } else if (s === "metadata") { val = col.value && t.data(col.value) ? t.data(col.value) : ""; }
  217. // put images instead of text if needed
  218. if (col.images) {
  219. img = col.images[val] || col.images["default"];
  220. if (img) {content = img[0] === "*" ? '<span class="'+img.substr(1)+'"></span>' : '<img src="'+img+'">';}
  221. } else { content = val; }
  222. // get the valueClass
  223. valClass = col.valueClass && t.attr(col.valueClass) ? t.attr(col.valueClass) : "";
  224. if (valClass && col.valueClassPrefix && col.valueClassPrefix !== "") {
  225. valClass = col.valueClassPrefix + valClass;
  226. }
  227. // get the wideValueClass
  228. wideValClass = col.wideValueClass && t.attr(col.wideValueClass) ? t.attr(col.wideValueClass) : "";
  229. if (wideValClass && col.wideValueClassPrefix && col.wideValueClassPrefix !== "") {
  230. wideValClass = col.wideValueClassPrefix + wideValClass;
  231. }
  232. // get the title
  233. title = col.title && t.attr(col.title) ? t.attr(col.title) : "";
  234. // strip out HTML
  235. title = title.replace(htmlstripre, '');
  236. // get the width
  237. paddingleft = 7;
  238. width = col.width || defaultWidth;
  239. tmpWidth = $.support.boxModel ? $(".jstree-grid-col-"+i+":first",t).width() : $(".jstree-grid-col-"+i+":first",t).outerWidth();
  240. width = tmpWidth || (width - paddingleft);
  241. last = isAlreadyGrid ? a.nextAll("div:eq("+(i-1)+")") : $("<div></div>").insertAfter(last);
  242. span = isAlreadyGrid ? last.children("span") : $("<span></span>").appendTo(last);
  243. // create a span inside the div, so we can control what happens in the whole div versus inside just the text/background
  244. span.addClass(cl+" "+valClass).css({"margin-right":"0px","display":"inline-block","*display":"inline","*+display":"inline"}).html(content).click((function (val,col,s) {
  245. return function() {
  246. $(this).trigger("select_cell.jstree-grid", [{value: val,column: col.header,node: $(this).closest("li"),sourceName: col.value,sourceType: s}]);
  247. };
  248. }(val,col,s)));
  249. last = last.css(conf).css({width: width,"padding-left":paddingleft+"px"}).addClass("jstree-grid-cell jstree-grid-cell-"+classAdd+" "+wcl+ " " + wideValClass + (tr?" ui-state-default":"")).addClass("jstree-grid-col-"+i);
  250. if (title) {
  251. span.attr("title",title);
  252. }
  253. }
  254. last.addClass("jstree-grid-cell-last"+(tr?" ui-state-default":""));
  255. $("<div></div>").css("clear","both").insertAfter(last);
  256. }
  257. });
  258. if(obj.is("li")) { this._repair_state(obj); }
  259. else { obj.find("> ul > li").each(function () { _this._repair_state(this); }); }
  260. $('.jstree').css({'overflow-y':'scroll'});
  261. }
  262. }
  263. // need to do alternating background colors or borders
  264. });
  265. }(jQuery));