IconTree.hx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package hide.comp;
  2. typedef IconTreeItem<T> = {
  3. var value : T;
  4. var text : String;
  5. @:optional var children : Bool;
  6. @:optional var icon : String;
  7. @:optional var state : {
  8. @:optional var opened : Bool;
  9. @:optional var selected : Bool;
  10. @:optional var disabled : Bool;
  11. @:optional var loaded : Bool;
  12. };
  13. @:optional var li_attr : Dynamic;
  14. @:optional var a_attr : Dynamic;
  15. @:optional @:noCompletion var id : String; // internal usage
  16. @:optional @:noCompletion var absKey : String; // internal usage
  17. }
  18. class IconTree<T:{}> extends Component {
  19. static var UID = 0;
  20. var waitRefresh = new Array<Void->Void>();
  21. var map : Map<String, IconTreeItem<T>> = new Map();
  22. var revMapString : haxe.ds.StringMap<IconTreeItem<T>> = new haxe.ds.StringMap();
  23. var revMap : haxe.ds.ObjectMap<T, IconTreeItem<T>> = new haxe.ds.ObjectMap();
  24. public var allowRename : Bool;
  25. public var async : Bool = false;
  26. public var autoOpenNodes = true;
  27. public function new(?parent,?el) {
  28. super(parent,el);
  29. element.addClass("tree");
  30. }
  31. public dynamic function get( parent : Null<T> ) : Array<IconTreeItem<T>> {
  32. return [{ value : null, text : "get()", children : true }];
  33. }
  34. public dynamic function onClick( e : T, evt: Dynamic) : Void {
  35. }
  36. public dynamic function onDblClick( e : T ) : Bool {
  37. return false;
  38. }
  39. public dynamic function onToggle( e : T, isOpen : Bool ) : Void {
  40. }
  41. public dynamic function onRename( e : T, value : String ) : Bool {
  42. return false;
  43. }
  44. public dynamic function onAllowMove( e : T, to : T ) : Bool {
  45. return false;
  46. }
  47. public dynamic function onMove( e : T, to : T, index : Int ) {
  48. }
  49. public dynamic function applyStyle( e : T, element : Element ) {
  50. }
  51. function makeContent(parent:IconTreeItem<T>) {
  52. var content : Array<IconTreeItem<T>> = get(parent == null ? null : parent.value);
  53. for( c in content ) {
  54. var key = (parent == null ? "" : parent.absKey + "/") + c.text;
  55. if( c.absKey == null ) c.absKey = key;
  56. c.id = "titem$" + (UID++);
  57. map.set(c.id, c);
  58. if( Std.is(c.value, String) )
  59. revMapString.set(cast c.value, c);
  60. else
  61. revMap.set(c.value, c);
  62. if( c.state == null ) {
  63. var s = getDisplayState(key);
  64. if( s != null ) c.state = { opened : s } else c.state = {};
  65. }
  66. if( !async && c.children ) {
  67. c.state.loaded = true;
  68. c.children = cast makeContent(c);
  69. }
  70. }
  71. return content;
  72. }
  73. public function init() {
  74. (element:Dynamic).jstree({
  75. core : {
  76. dblclick_toggle: false,
  77. animation: 50,
  78. themes: {
  79. name: "default-dark",
  80. dots: true,
  81. icons: true
  82. },
  83. check_callback : function(operation, node, node_parent, value, extra) {
  84. if( operation == "edit" && allowRename )
  85. return true;
  86. if(!map.exists(node.id)) // Can happen on drag from foreign tree
  87. return false;
  88. if( operation == "rename_node" ) {
  89. if( node.text == value ) return true; // no change
  90. return onRename(map.get(node.id).value, value);
  91. }
  92. if( operation == "move_node" ) {
  93. if( extra.ref == null ) return true;
  94. return onAllowMove(map.get(node.id).value, map.get(extra.ref.id).value);
  95. }
  96. return false;
  97. },
  98. data : function(obj, callb) {
  99. callb.call(this, makeContent(obj.parent == null ? null : map.get(obj.id)));
  100. }
  101. },
  102. plugins : [ "dnd", "changed" ],
  103. });
  104. element.on("click.jstree", function (event) {
  105. var node = new Element(event.target).closest("li");
  106. if(node == null || node.length == 0) return;
  107. var i = map.get(node[0].id);
  108. onClick(i.value, event);
  109. });
  110. element.on("dblclick.jstree", function (event) {
  111. // ignore dblclick on open/close arrow
  112. if( event.target.className.indexOf("jstree-ocl") >= 0 )
  113. return;
  114. var node = new Element(event.target).closest("li");
  115. var i = map.get(node[0].id);
  116. if(onDblClick(i.value))
  117. return;
  118. if( allowRename ) {
  119. // ignore rename on icon
  120. if( event.target.className.indexOf("jstree-icon") >= 0 )
  121. return;
  122. editNode(i.value);
  123. return;
  124. }
  125. });
  126. element.on("open_node.jstree", function(event, e) {
  127. var i = map.get(e.node.id);
  128. saveDisplayState(i.absKey, true);
  129. onToggle(i.value, true);
  130. });
  131. element.on("close_node.jstree", function(event,e) {
  132. var i = map.get(e.node.id);
  133. saveDisplayState(i.absKey, false);
  134. onToggle(i.value, false);
  135. });
  136. element.on("refresh.jstree", function(_) {
  137. var old = waitRefresh;
  138. waitRefresh = [];
  139. for( f in old ) f();
  140. });
  141. element.on("move_node.jstree", function(event, e) {
  142. onMove(map.get(e.node.id).value, e.parent == "#" ? null : map.get(e.parent).value, e.position);
  143. });
  144. element.on('ready.jstree', function () {
  145. var lis = element.find("li");
  146. for(li in lis) {
  147. var item = map.get(li.id);
  148. if(item != null)
  149. applyStyle(item.value, new Element(li));
  150. }
  151. });
  152. element.on('changed.jstree', function (e, data) {
  153. var nodes: Array<Dynamic> = data.changed.deselected;
  154. for(id in nodes) {
  155. var item = map.get(id).value;
  156. var el = getElement(item);
  157. applyStyle(item, el);
  158. }
  159. });
  160. element.on("rename_node.jstree", function(e, data) {
  161. var item = map.get(data.node.id).value;
  162. var el = getElement(item);
  163. applyStyle(item, el);
  164. });
  165. element.on("after_open.jstree", function(event, data) {
  166. var lis = new Element(event.target).find("li");
  167. for(li in lis) {
  168. var item = map.get(li.id);
  169. if(item != null)
  170. applyStyle(item.value, new Element(li));
  171. }
  172. });
  173. }
  174. function getRev( o : T ) {
  175. if( Std.is(o, String) )
  176. return revMapString.get(cast o);
  177. return revMap.get(o);
  178. }
  179. public function getElement(e : T) : Element {
  180. var v = getRev(e);
  181. if(v == null)
  182. return null;
  183. var el = (element:Dynamic).jstree('get_node', v.id, true);
  184. return el;
  185. }
  186. public function editNode( e : T ) {
  187. var n = getRev(e).id;
  188. (element:Dynamic).jstree('edit',n);
  189. }
  190. public function getCurrentOver() : Null<T> {
  191. var id = element.find(":focus").attr("id");
  192. if( id == null )
  193. return null;
  194. var i = map.get(id.substr(0, -7)); // remove _anchor
  195. return i == null ? null : i.value;
  196. }
  197. public function setSelection( objects : Array<T> ) {
  198. (element:Dynamic).jstree('deselect_all');
  199. var ids = [for( o in objects ) { var v = getRev(o); if( v != null ) v.id; }];
  200. (element:Dynamic).jstree('select_node', ids, false, !autoOpenNodes); // Don't auto-open parent
  201. if(autoOpenNodes)
  202. for(obj in objects)
  203. revealNode(obj);
  204. }
  205. public function refresh( ?onReady : Void -> Void ) {
  206. map = new Map();
  207. revMap = new haxe.ds.ObjectMap();
  208. revMapString = new haxe.ds.StringMap();
  209. if( onReady != null ) waitRefresh.push(onReady);
  210. (element:Dynamic).jstree('refresh',true);
  211. }
  212. public function getSelection() : Array<T> {
  213. var ids : Array<String> = (element:Dynamic).jstree('get_selected');
  214. return [for( id in ids ) map.get(id).value];
  215. }
  216. public function collapseAll() {
  217. (element:Dynamic).jstree('close_all');
  218. }
  219. public function openNode(e: T) {
  220. var v = getRev(e);
  221. if(v == null) return;
  222. (element:Dynamic).jstree('_open_to', v.id);
  223. }
  224. public function revealNode(e : T) {
  225. openNode(e);
  226. var el = getElement(e);
  227. if(el != null)
  228. (el[0] : Dynamic).scrollIntoViewIfNeeded();
  229. }
  230. public function searchFilter( filter : String ) {
  231. if( filter == "" ) filter = null;
  232. if( filter != null ) filter = filter.toLowerCase();
  233. var lines = element.find(".jstree-node");
  234. lines.removeClass("filtered");
  235. if( filter != null ) {
  236. for( t in lines ) {
  237. if( t.textContent.toLowerCase().indexOf(filter) < 0 )
  238. t.classList.add("filtered");
  239. }
  240. while( lines.length > 0 ) {
  241. lines = lines.filter(".list").not(".filtered").prev();
  242. lines.removeClass("filtered");
  243. }
  244. }
  245. }
  246. }