mif.tree_for-mt-1.3.2.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250
  1. /*
  2. ---
  3. name: Mif.Tree
  4. description: Mif.Tree base Class
  5. license: MIT-Style License (http://mifjs.net/license.txt)
  6. copyright: Anton Samoylov (http://mifjs.net)
  7. authors: Anton Samoylov (http://mifjs.net)
  8. requires:
  9. - Core:1.3/*
  10. - More/Fx.Scroll
  11. - More/Fx.Drag
  12. provides: Mif.Tree
  13. ...
  14. */
  15. if(!Mif) var Mif = {};
  16. if(!Mif.ids) Mif.ids = {};
  17. if(!Mif.id) Mif.id = function(id){
  18. return Mif.ids[id];
  19. };
  20. Mif.Tree = new Class({
  21. version: '1.2.6.4',
  22. Implements: [Events, Options],
  23. options:{
  24. types: {},
  25. forest: false,
  26. animateScroll: true,
  27. height: 18,
  28. expandTo: true
  29. },
  30. initialize: function(options){
  31. this.setOptions(options);
  32. Object.append(this, {
  33. types: this.options.types,
  34. forest: this.options.forest,
  35. animateScroll: this.options.animateScroll,
  36. dfltType: this.options.dfltType,
  37. height: this.options.height,
  38. container: $(options.container),
  39. UID: ++Mif.Tree.UID,
  40. key: {},
  41. expanded: []
  42. });
  43. this.defaults = {
  44. name: '',
  45. cls: '',
  46. openIcon: 'mif-tree-empty-icon',
  47. closeIcon: 'mif-tree-empty-icon',
  48. loadable: false,
  49. hidden: false
  50. };
  51. this.dfltState = {
  52. open: false
  53. };
  54. this.$index = [];
  55. this.updateOpenState();
  56. if(this.options.expandTo) this.initExpandTo();
  57. this.DOMidPrefix='mif-tree-';
  58. this.wrapper = new Element('div').addClass('mif-tree-wrapper').inject(this.container,'bottom');
  59. this.events();
  60. this.initScroll();
  61. this.initSelection();
  62. this.initHover();
  63. this.addEvent('drawChildren', function(parent){
  64. var nodes = parent._toggle||[];
  65. for(var i = 0, l = nodes.length; i < l; i++){
  66. nodes[i].drawToggle();
  67. }
  68. parent._toggle = [];
  69. });
  70. var id = this.options.id;
  71. this.id = id;
  72. if(id != null) Mif.ids[id] = this;
  73. if (MooTools.version >= '1.2.2' && this.options.initialize) this.options.initialize.call(this);
  74. },
  75. bound: function(){
  76. Array.each(arguments, function(name){
  77. this.bound[name] = this[name].bind(this);
  78. }, this);
  79. },
  80. events: function(){
  81. this.bound('mouse', 'mouseleave', 'mousedown', 'preventDefault', 'toggleClick', 'toggleDblclick', 'focus', 'blurOnClick', 'keyDown', 'keyUp');
  82. this.wrapper.addEvents({
  83. mousemove: this.bound.mouse,
  84. mouseover: this.bound.mouse,
  85. mouseout: this.bound.mouse,
  86. mouseleave: this.bound.mouseleave,
  87. mousedown: this.bound.mousedown,
  88. click: this.bound.toggleClick,
  89. dblclick: this.bound.toggleDblclick,
  90. selectstart: this.bound.preventDefault
  91. });
  92. this.container.addEvent('click', this.bound.focus);
  93. document.addEvent('click', this.bound.blurOnClick);
  94. document.addEvents({
  95. keydown: this.bound.keyDown,
  96. keyup: this.bound.keyUp
  97. });
  98. },
  99. blurOnClick: function(event){
  100. var target = event.target;
  101. while(target){
  102. if(target == this.container) return;
  103. target = target.parentNode;
  104. }
  105. this.blur();
  106. },
  107. focus: function(){
  108. if(Mif.Focus && Mif.Focus == this) return this;
  109. if(Mif.Focus) Mif.Focus.blur();
  110. Mif.Focus = this;
  111. this.focused = true;
  112. this.container.addClass('mif-tree-focused');
  113. return this.fireEvent('focus');
  114. },
  115. blur: function(){
  116. Mif.Focus = null;
  117. if(!this.focused) return this;
  118. this.focused = false;
  119. this.container.removeClass('mif-tree-focused');
  120. return this.fireEvent('blur');
  121. },
  122. $getIndex: function(){//return array of visible nodes.
  123. this.$index = [];
  124. var node = this.forest ? this.root.getFirst() : this.root;
  125. var previous = node;
  126. while(node){
  127. if(!(previous.hidden && previous.contains(node))){
  128. if(!node.hidden) this.$index.push(node);
  129. previous = node;
  130. }
  131. node = node._getNextVisible();
  132. }
  133. },
  134. preventDefault: function(event){
  135. event.preventDefault();
  136. },
  137. mousedown: function(event){
  138. if(event.rightClick) return;
  139. event.preventDefault();
  140. this.fireEvent('mousedown');
  141. },
  142. mouseleave: function(){
  143. this.mouse.coords = {x: null,y: null};
  144. this.mouse.target = false;
  145. this.mouse.node = false;
  146. if(this.hover) this.hover();
  147. },
  148. mouse: function(event){
  149. this.mouse.coords = this.getCoords(event);
  150. var target = this.getTarget(event);
  151. this.mouse.target = target.target;
  152. this.mouse.node = target.node;
  153. },
  154. getTarget: function(event){
  155. var target = event.target, node;
  156. while(!(/mif-tree/).test(target.className)){
  157. target = target.parentNode;
  158. }
  159. var test = target.className.match(/mif-tree-(gadjet)-[^n]|mif-tree-(icon)|mif-tree-(name)|mif-tree-(checkbox)/);
  160. if(!test){
  161. var y = this.mouse.coords.y;
  162. if(y == -1||!this.$index) {
  163. node = false;
  164. }else{
  165. node = this.$index[((y)/this.height).toInt()];
  166. }
  167. return {
  168. node: node,
  169. target: 'node'
  170. };
  171. }
  172. for(var i = 5; i > 0; i--){
  173. if(test[i]){
  174. var type = test[i];
  175. break;
  176. }
  177. }
  178. return {
  179. node: Mif.Tree.Nodes[target.getAttribute('uid')],
  180. target: type
  181. };
  182. },
  183. getCoords: function(event){
  184. var position = this.wrapper.getPosition();
  185. var x = event.page.x - position.x;
  186. var y = event.page.y - position.y;
  187. var wrapper = this.wrapper;
  188. if((y-wrapper.scrollTop > wrapper.clientHeight)||(x - wrapper.scrollLeft > wrapper.clientWidth)){//scroll line
  189. y = -1;
  190. };
  191. return {x: x, y: y};
  192. },
  193. keyDown: function(event){
  194. this.key = event;
  195. this.key.state = 'down';
  196. if(this.focused) this.fireEvent('keydown', [event]);
  197. },
  198. keyUp: function(event){
  199. this.key = {};
  200. this.key.state = 'up';
  201. if(this.focused) this.fireEvent('keyup', [event]);
  202. },
  203. toggleDblclick: function(event){
  204. var target = this.mouse.target;
  205. if(!(target == 'name' || target == 'icon')) return;
  206. this.mouse.node.toggle();
  207. },
  208. toggleClick: function(event){
  209. if(this.mouse.target != 'gadjet') return;
  210. this.mouse.node.toggle();
  211. },
  212. initScroll: function(){
  213. this.scroll = new Fx.Scroll(this.wrapper, {link: 'cancel'});
  214. },
  215. scrollTo: function(node){
  216. var position = node.getVisiblePosition();
  217. var top = position*this.height;
  218. var up = (top < this.wrapper.scrollTop);
  219. var down = (top > (this.wrapper.scrollTop + this.wrapper.clientHeight - this.height));
  220. if(position == -1 || ( !up && !down ) ) {
  221. this.scroll.fireEvent('complete');
  222. return false;
  223. }
  224. if(this.animateScroll){
  225. this.scroll.start(this.wrapper.scrollLeft, top - (down ? this.wrapper.clientHeight - this.height : this.height));
  226. }else{
  227. this.scroll.set(this.wrapper.scrollLeft, top - (down ? this.wrapper.clientHeight - this.height : this.height));
  228. this.scroll.fireEvent('complete');
  229. }
  230. return this;
  231. },
  232. updateOpenState: function(){
  233. this.addEvents({
  234. 'drawChildren': function(parent){
  235. var children = parent.children;
  236. for(var i = 0, l = children.length; i < l; i++){
  237. children[i].updateOpenState();
  238. }
  239. },
  240. 'drawRoot': function(){
  241. this.root.updateOpenState();
  242. }
  243. });
  244. },
  245. expandTo: function(node){
  246. if (!node) return this;
  247. var path = [];
  248. while( !node.isRoot() && !(this.forest && node.getParent().isRoot()) ){
  249. node = node.getParent();
  250. if(!node) break;
  251. path.unshift(node);
  252. };
  253. path.each(function(el){
  254. el.toggle(true);
  255. });
  256. return this;
  257. },
  258. initExpandTo: function(){
  259. this.addEvent('loadChildren', function(parent){
  260. if(!parent) return;
  261. var children = parent.children;
  262. for( var i = children.length; i--; ){
  263. var child = children[i];
  264. if(child.expandTo) this.expanded.push(child);
  265. }
  266. });
  267. function expand(){
  268. this.expanded.each(function(node){
  269. this.expandTo(node);
  270. }, this);
  271. this.expanded = [];
  272. };
  273. this.addEvents({
  274. 'load': expand.bind(this),
  275. 'loadNode': expand.bind(this)
  276. });
  277. }
  278. });
  279. Mif.Tree.UID = 0;
  280. Array.implement({
  281. inject: function(added, current, where){//inject added after or before current;
  282. var pos = this.indexOf(current) + (where == 'before' ? 0 : 1);
  283. for(var i = this.length-1; i >= pos; i--){
  284. this[i + 1] = this[i];
  285. }
  286. this[pos] = added;
  287. return this;
  288. }
  289. });
  290. /*
  291. ---
  292. name: Mif.Tree.Node
  293. description: Mif.Tree.Node
  294. license: MIT-Style License (http://mifjs.net/license.txt)
  295. copyright: Anton Samoylov (http://mifjs.net)
  296. authors: Anton Samoylov (http://mifjs.net)
  297. requires: Mif.Tree
  298. provides: Mif.Tree.Node
  299. ...
  300. */
  301. Mif.Tree.Node = new Class({
  302. Implements: [Events],
  303. initialize: function(structure, options) {
  304. Object.append(this, structure);
  305. this.children = [];
  306. this.type = options.type || this.tree.dfltType;
  307. this.property = options.property || {};
  308. this.data = options.data;
  309. this.state = Object.append(Object.clone(this.tree.dfltState), options.state);
  310. this.$calculate();
  311. this.UID = Mif.Tree.Node.UID++;
  312. Mif.Tree.Nodes[this.UID] = this;
  313. var id = this.id;
  314. if(id != null) Mif.ids[id] = this;
  315. this.tree.fireEvent('nodeCreate', [this]);
  316. this._property = ['id', 'name', 'cls', 'openIcon', 'closeIcon', 'openIconUrl', 'closeIconUrl', 'hidden'];
  317. },
  318. $calculate: function(){
  319. Object.append(this, Object.clone(this.tree.defaults));
  320. this.type = Array.from(this.type);
  321. this.type.each(function(type){
  322. var props = this.tree.types[type];
  323. if(props) Object.append(this, props);
  324. }, this);
  325. Object.append(this, this.property);
  326. return this;
  327. },
  328. getDOM: function(what){
  329. var node = $(this.tree.DOMidPrefix+this.UID);
  330. if(what == 'node') return node;
  331. var wrapper = node.getFirst();
  332. if(what == 'wrapper') return wrapper;
  333. if(what == 'children') return wrapper.getNext();
  334. return wrapper.getElement('.mif-tree-'+what);
  335. },
  336. getGadjetType: function(){
  337. return (this.loadable && !this.isLoaded()) ? 'plus' : (this.hasVisibleChildren() ? (this.isOpen() ? 'minus' : 'plus') : 'none');
  338. },
  339. toggle: function(state) {
  340. if(this.state.open == state || this.$loading || this.$toggling) return this;
  341. var parent = this.getParent();
  342. function toggle(type){
  343. this.state.open = !this.state.open;
  344. if(type == 'drawed'){
  345. this.drawToggle();
  346. }else{
  347. parent._toggle = (parent._toggle||[])[this.state.open ? 'include' : 'erase'](this);
  348. }
  349. this.fireEvent('toggle', [this.state.open]);
  350. this.tree.fireEvent('toggle', [this, this.state.open]);
  351. return this;
  352. }
  353. if(parent && !parent.$draw){
  354. return toggle.apply(this, []);
  355. }
  356. if(this.loadable && !this.state.loaded) {
  357. if(!this.load_event){
  358. this.load_event = true;
  359. this.addEvent('load',function(){
  360. this.toggle();
  361. }.bind(this));
  362. }
  363. return this.load();
  364. }
  365. if(!this.hasChildren()) return this;
  366. return toggle.apply(this, ['drawed']);
  367. },
  368. drawToggle: function(){
  369. this.tree.$getIndex();
  370. Mif.Tree.Draw.update(this);
  371. },
  372. recursive: function(fn, args){
  373. args=Array.from(args);
  374. if(fn.apply(this, args) !== false){
  375. this.children.each(function(node){
  376. if(node.recursive(fn, args) === false){
  377. return false;
  378. }
  379. });
  380. }
  381. return this;
  382. },
  383. isOpen: function(){
  384. return this.state.open;
  385. },
  386. isLoaded: function(){
  387. return this.state.loaded;
  388. },
  389. isLast: function(){
  390. if(this.parentNode == null || this.parentNode.children.getLast() == this) return true;
  391. return false;
  392. },
  393. isFirst: function(){
  394. if(this.parentNode == null || this.parentNode.children[0] == this) return true;
  395. return false;
  396. },
  397. isRoot: function(){
  398. return this.parentNode == null ? true : false;
  399. },
  400. getChildren: function(){
  401. return this.children;
  402. },
  403. hasChildren: function(){
  404. return this.children.length ? true : false;
  405. },
  406. index: function(){
  407. if( this.isRoot() ) return 0;
  408. return this.parentNode.children.indexOf(this);
  409. },
  410. getNext: function(){
  411. if(this.isLast()) return null;
  412. return this.parentNode.children[this.index()+1];
  413. },
  414. getPrevious: function(){
  415. if( this.isFirst() ) return null;
  416. return this.parentNode.children[this.index()-1];
  417. },
  418. getFirst: function(){
  419. if(!this.hasChildren()) return null;
  420. return this.children[0];
  421. },
  422. getLast: function(){
  423. if(!this.hasChildren()) return null;
  424. return this.children.getLast();
  425. },
  426. getParent: function(){
  427. return this.parentNode;
  428. },
  429. _getNextVisible: function(){
  430. var current=this;
  431. if(current.isRoot()){
  432. if(!current.isOpen() || !current.hasChildren(true)) return false;
  433. return current.getFirst(true);
  434. }else{
  435. if(current.isOpen() && current.getFirst(true)){
  436. return current.getFirst(true);
  437. }else{
  438. var parent = current;
  439. do{
  440. current = parent.getNext(true);
  441. if(current) return current;
  442. parent = parent.parentNode;
  443. }while(parent);
  444. return false;
  445. }
  446. }
  447. },
  448. getPreviousVisible: function(){
  449. var index = this.tree.$index.indexOf(this);
  450. return index == 0 ? null : this.tree.$index[index-1];
  451. },
  452. getNextVisible: function(){
  453. var index = this.tree.$index.indexOf(this);
  454. return index < this.tree.$index.length-1 ? this.tree.$index[index+1] : null;
  455. },
  456. getVisiblePosition: function(){
  457. return this.tree.$index.indexOf(this);
  458. },
  459. hasVisibleChildren: function(){
  460. if(!this.hasChildren()) return false;
  461. if(this.isOpen()){
  462. var next = this.getNextVisible();
  463. if(!next) return false;
  464. if(next.parentNode != this) return false;
  465. return true;
  466. }else{
  467. var child = this.getFirst();
  468. while(child){
  469. if(!child.hidden) return true;
  470. child = child.getNext();
  471. }
  472. return false;
  473. }
  474. },
  475. isLastVisible: function(){
  476. var next = this.getNext();
  477. while(next){
  478. if(!next.hidden) return false;
  479. next = next.getNext();
  480. };
  481. return true;
  482. },
  483. contains: function(node){
  484. while(node){
  485. if(node == this) return true;
  486. node = node.parentNode;
  487. };
  488. return false;
  489. },
  490. addType: function(type){
  491. return this.processType(type, 'add');
  492. },
  493. removeType: function(type){
  494. return this.processType(type, 'remove');
  495. },
  496. setType: function(type){
  497. return this.processType(type, 'set');
  498. },
  499. processType: function(type, action){
  500. switch(action){
  501. case 'add': this.type.include(type); break;
  502. case 'remove': this.type.erase(type); break;
  503. case 'set': this.type = type; break;
  504. }
  505. var current = {};
  506. this._property.each(function(p){
  507. current[p] = this[p];
  508. }, this);
  509. this.$calculate();
  510. this._property.each(function(p){
  511. this.updateProperty(p, current[p], this[p]);
  512. }, this);
  513. return this;
  514. },
  515. set: function(obj){
  516. this.tree.fireEvent('beforeSet', [this, obj]);
  517. var property = obj.property||obj||{};
  518. for(var p in property){
  519. var nv = property[p];
  520. var cv = this[p];
  521. this.updateProperty(p, cv, nv);
  522. this[p] = this.property[p] = nv;
  523. }
  524. this.tree.fireEvent('set', [this, obj]);
  525. return this;
  526. },
  527. updateProperty: function(p, cv, nv){
  528. if(nv == cv) return this;
  529. if(p == 'id'){
  530. delete Mif.ids[cv];
  531. if(nv) Mif.ids[nv]=this;
  532. return this;
  533. }
  534. if(!Mif.Tree.Draw.isUpdatable(this)) return this;
  535. switch(p){
  536. case 'name':
  537. this.getDOM('name').set('html', nv);
  538. return this;
  539. case 'cls':
  540. this.getDOM('wrapper').removeClass(cv).addClass(nv);
  541. return this;
  542. case 'openIcon':
  543. case 'closeIcon':
  544. this.getDOM('icon').removeClass(cv).addClass(nv);
  545. return this;
  546. case 'openIconUrl':
  547. case 'closeIconUrl':
  548. var icon = this.getDOM('icon');
  549. icon.setStyle('background-image', 'none');
  550. if(nv) icon.setStyle('background-image', 'url('+nv+')');
  551. return this;
  552. case 'hidden':
  553. this.getDOM('node').setStyle('display', nv ? 'none' : 'block');
  554. var _previous = this.getPreviousVisible();
  555. var _next = this.getNextVisible();
  556. var parent = this.getParent();
  557. this[p] = this.property[p]=nv;
  558. this.tree.$getIndex();
  559. var previous = this.getPreviousVisible();
  560. var next = this.getNextVisible();
  561. [_previous, _next, previous, next, parent, this].each(function(node){
  562. Mif.Tree.Draw.update(node);
  563. });
  564. return this;
  565. }
  566. return this;
  567. },
  568. updateOpenState: function(){
  569. if(this.state.open){
  570. this.state.open = false;
  571. this.toggle();
  572. }
  573. }
  574. });
  575. Mif.Tree.Node.UID = 0;
  576. Mif.Tree.Nodes = {};
  577. /*
  578. ---
  579. name: Mif.Tree.Draw
  580. description: convert javascript tree object to html
  581. license: MIT-Style License (http://mifjs.net/license.txt)
  582. copyright: Anton Samoylov (http://mifjs.net)
  583. authors: Anton Samoylov (http://mifjs.net)
  584. requires: Mif.Tree
  585. provides: Mif.Tree.Draw
  586. ...
  587. */
  588. Mif.Tree.Draw = {
  589. getHTML: function(node,html){
  590. var prefix = node.tree.DOMidPrefix;
  591. var checkbox;
  592. if(node.state.checked != undefined){
  593. if(!node.hasCheckbox) node.state.checked='nochecked';
  594. checkbox = '<span class="mif-tree-checkbox mif-tree-node-'+node.state.checked+'" uid="'+node.UID+'">'+Mif.Tree.Draw.zeroSpace+'</span>';
  595. }else{
  596. checkbox = '';
  597. }
  598. html = html||[];
  599. html.push(
  600. '<div class="mif-tree-node ',(node.isLast() ? 'mif-tree-node-last' : ''),'"'+(node.hidden ? ' style="display:none"' : '')+' id="',prefix,node.UID,'">',
  601. '<span class="mif-tree-node-wrapper ',node.cls,(node.state.selected ? ' mif-tree-node-selected' : ''),'" uid="',node.UID,'">',
  602. '<span class="mif-tree-gadjet mif-tree-gadjet-',node.getGadjetType(),'" uid="',node.UID,'">',Mif.Tree.Draw.zeroSpace,'</span>',
  603. checkbox,
  604. '<span class="mif-tree-icon ',(node.closeIconUrl?'" style="background-image: url('+node.closeIconUrl+')" ': node.closeIcon+'"'),' uid="',node.UID,'">',Mif.Tree.Draw.zeroSpace,'</span>',
  605. '<span class="mif-tree-name" uid="',node.UID,'">',node.name,'</span>',
  606. '</span>',
  607. '<div class="mif-tree-children" style="display:none"></div>',
  608. '</div>'
  609. );
  610. return html;
  611. },
  612. children: function(parent, container){
  613. parent.open = true;
  614. parent.$draw = true;
  615. var html = [];
  616. var children = parent.children;
  617. for(var i = 0, l = children.length; i < l; i++){
  618. this.getHTML(children[i], html);
  619. }
  620. container = container || parent.getDOM('children');
  621. container.set('html', html.join(''));
  622. parent.tree.fireEvent('drawChildren',[parent]);
  623. },
  624. root: function(tree){
  625. var domRoot = this.node(tree.root);
  626. domRoot.inject(tree.wrapper);
  627. tree.$draw = true;
  628. tree.fireEvent('drawRoot');
  629. },
  630. forestRoot: function(tree){
  631. var container = new Element('div').addClass('mif-tree-children-root').inject(tree.wrapper,'bottom');
  632. Mif.Tree.Draw.children(tree.root, container);
  633. },
  634. node: function(node){
  635. return new Element('div').set('html', this.getHTML(node).join('')).getFirst();
  636. },
  637. isUpdatable: function(node){
  638. if(
  639. (!node||!node.tree) ||
  640. (node.getParent() && !node.getParent().$draw) ||
  641. (node.isRoot() && (!node.tree.$draw||node.tree.forest))
  642. ) return false;
  643. return true;
  644. },
  645. update: function(node){
  646. if(!this.isUpdatable(node)) return null;
  647. if(!node.hasChildren()) node.state.open = false;
  648. node.getDOM('gadjet').className = 'mif-tree-gadjet mif-tree-gadjet-'+node.getGadjetType();
  649. if (node.closeIconUrl) {
  650. node.getDOM('icon').setStyle('background-image', 'url('+(node.isOpen() ? node.openIconUrl : node.closeIconUrl)+')');
  651. } else {
  652. node.getDOM('icon').className = 'mif-tree-icon '+node[node.isOpen() ? 'openIcon' : 'closeIcon'];
  653. }
  654. node.getDOM('node')[(node.isLastVisible() ?'add' : 'remove')+'Class']('mif-tree-node-last');
  655. if(node.$loading) return null;
  656. var children = node.getDOM('children');
  657. if(node.isOpen()){
  658. if(!node.$draw) Mif.Tree.Draw.children(node);
  659. children.style.display = 'block';
  660. }else{
  661. children.style.display = 'none';
  662. }
  663. node.tree.fireEvent('updateNode', node);
  664. return node;
  665. },
  666. inject: function(node, element){
  667. if(!this.isUpdatable(node)) return;
  668. element = element || node.getDOM('node') || this.node(node);
  669. var previous = node.getPrevious();
  670. if(previous){
  671. element.inject(previous.getDOM('node'),'after');
  672. return;
  673. }
  674. var container;
  675. if(node.tree.forest && node.parentNode.isRoot()){
  676. container = node.tree.wrapper.getElement('.mif-tree-children-root');
  677. }else if(node.tree.root == node){
  678. container = node.tree.wrapper;
  679. }else{
  680. container = node.parentNode.getDOM('children');
  681. }
  682. element.inject(container, 'top');
  683. }
  684. };
  685. Mif.Tree.Draw.zeroSpace = Browser.ie ? '&shy;' : (Browser.chrome ? '&#8203' : '');
  686. /*
  687. ---
  688. name: Mif.Tree.Selection
  689. description: tree nodes selection
  690. license: MIT-Style License (http://mifjs.net/license.txt)
  691. copyright: Anton Samoylov (http://mifjs.net)
  692. authors: Anton Samoylov (http://mifjs.net)
  693. requires: Mif.Tree
  694. provides: Mif.Tree.Selection
  695. ...
  696. */
  697. Mif.Tree.implement({
  698. initSelection: function(){
  699. this.defaults.selectClass = '';
  700. this.wrapper.addEvent('mousedown', function(e) {
  701. //this.attachSelect.bindWithEvent(this)
  702. this.attachSelect(e)
  703. }.bind(this)
  704. );
  705. },
  706. attachSelect: function(event){
  707. if(!['icon', 'name', 'node'].contains(this.mouse.target)) return;
  708. var node = this.mouse.node;
  709. if(!node) return;
  710. this.select(node);
  711. },
  712. select: function(node) {
  713. if(!node) return this;
  714. var current = this.selected;
  715. if (current == node) return this;
  716. if (current) {
  717. current.select(false);
  718. this.fireEvent('unSelect', [current]).fireEvent('selectChange', [current, false]);
  719. }
  720. this.selected = node;
  721. node.select(true);
  722. this.fireEvent('select', [node]).fireEvent('selectChange', [node, true]);
  723. return this;
  724. },
  725. unselect: function(){
  726. var current = this.selected;
  727. if(!current) return this;
  728. this.selected = false;
  729. current.select(false);
  730. this.fireEvent('unSelect', [current]).fireEvent('selectChange', [current, false]);
  731. return this;
  732. },
  733. getSelected: function(){
  734. return this.selected;
  735. },
  736. isSelected: function(node){
  737. return node.isSelected();
  738. }
  739. });
  740. Mif.Tree.Node.implement({
  741. select: function(state) {
  742. this.state.selected = state;
  743. if(!Mif.Tree.Draw.isUpdatable(this)) return;
  744. var wrapper=this.getDOM('wrapper');
  745. wrapper[(state ? 'add' : 'remove')+'Class'](this.selectClass||'mif-tree-node-selected');
  746. },
  747. isSelected: function(){
  748. return this.state.selected;
  749. }
  750. });
  751. /*
  752. ---
  753. name: Mif.Tree.Hover
  754. description: hover(mouseover/mouseout) events/effects
  755. license: MIT-Style License (http://mifjs.net/license.txt)
  756. copyright: Anton Samoylov (http://mifjs.net)
  757. authors: Anton Samoylov (http://mifjs.net)
  758. requires: Mif.Tree
  759. provides: Mif.Tree.Hover
  760. ...
  761. */
  762. Mif.Tree.implement({
  763. initHover: function(){
  764. this.defaults.hoverClass = '';
  765. this.wrapper.addEvent('mousemove', this.hover.bind(this));
  766. this.wrapper.addEvent('mouseout', this.hover.bind(this));
  767. this.defaultHoverState = {
  768. gadjet: false,
  769. checkbox: false,
  770. icon: false,
  771. name: false,
  772. node: false
  773. };
  774. this.hoverState = Object.clone(this.defaultHoverState);
  775. },
  776. hover: function(){
  777. var cnode = this.mouse.node;
  778. var ctarget = this.mouse.target;
  779. Object.each(this.hoverState, function(node, target, state){
  780. if(node == cnode && (target == 'node'||target==ctarget)) return;
  781. if(node) {
  782. Mif.Tree.Hover.out(node, target);
  783. state[target] = false;
  784. this.fireEvent('hover', [node, target, 'out']);
  785. }
  786. if(cnode && (target == 'node'||target == ctarget)) {
  787. Mif.Tree.Hover.over(cnode, target);
  788. state[target] = cnode;
  789. this.fireEvent('hover', [cnode, target, 'over']);
  790. }else{
  791. state[target] = false;
  792. }
  793. }, this);
  794. },
  795. updateHover: function(){
  796. this.hoverState = Object.clone(this.defaultHoverState);
  797. this.hover();
  798. }
  799. });
  800. Mif.Tree.Hover = {
  801. over: function(node, target){
  802. var wrapper = node.getDOM('wrapper');
  803. wrapper.addClass((node.hoverClass||'mif-tree-hover')+'-'+target);
  804. if(node.state.selected) wrapper.addClass((node.hoverClass||'mif-tree-hover')+'-selected-'+target);
  805. },
  806. out: function(node, target){
  807. var wrapper = node.getDOM('wrapper');
  808. wrapper.removeClass((node.hoverClass||'mif-tree-hover')+'-'+target).removeClass((node.hoverClass||'mif-tree-hover')+'-selected-'+target);
  809. }
  810. };
  811. /*
  812. ---
  813. name: Mif.Tree.Load
  814. description: load tree from json
  815. license: MIT-Style License (http://mifjs.net/license.txt)
  816. copyright: Anton Samoylov (http://mifjs.net)
  817. authors: Anton Samoylov (http://mifjs.net)
  818. requires: Mif.Tree
  819. provides: Mif.Tree.Load
  820. ...
  821. */
  822. Mif.Tree.Load = {
  823. children: function(children, parent, tree){
  824. var i, l;
  825. var subChildrens = [];
  826. for(i = children.length; i--; ){
  827. var child = children[i];
  828. var node = new Mif.Tree.Node({
  829. tree: tree,
  830. parentNode: parent||undefined
  831. }, child);
  832. if( tree.forest || parent != undefined){
  833. parent.children.unshift(node);
  834. }else{
  835. tree.root = node;
  836. }
  837. var subChildren = child.children;
  838. if(subChildren && subChildren.length){
  839. subChildrens.push({children: subChildren, parent: node});
  840. }
  841. }
  842. for(i = 0, l = subChildrens.length; i < l; i++) {
  843. var sub = subChildrens[i];
  844. arguments.callee(sub.children, sub.parent, tree);
  845. }
  846. if(parent) parent.state.loaded = true;
  847. tree.fireEvent('loadChildren', parent);
  848. }
  849. };
  850. Mif.Tree.implement({
  851. load: function(options){
  852. var tree = this;
  853. this.loadOptions = this.loadOptions||Function.from({});
  854. function success(json){
  855. var parent = null;
  856. if(tree.forest){
  857. tree.root = new Mif.Tree.Node({
  858. tree: tree,
  859. parentNode: null
  860. }, {});
  861. parent = tree.root;
  862. }
  863. Mif.Tree.Load.children(json, parent, tree);
  864. Mif.Tree.Draw[tree.forest ? 'forestRoot' : 'root'](tree);
  865. tree.$getIndex();
  866. tree.fireEvent('load');
  867. return tree;
  868. }
  869. options = Object.append(Object.append({
  870. isSuccess: Function.from(true),
  871. secure: true,
  872. onSuccess: success,
  873. onError: errorFunc,
  874. method: 'get'
  875. }, this.loadOptions()), options);
  876. function errorFunc(text,error) {
  877. alert(text);
  878. alert(error);
  879. return false;
  880. }
  881. if(options.json) return success(options.json);
  882. new Request.JSON(options).send();
  883. return this;
  884. }
  885. });
  886. Mif.Tree.Node.implement({
  887. load: function(options){
  888. this.$loading = true;
  889. options = options||{};
  890. this.addType('loader');
  891. var self = this;
  892. function success(json){
  893. Mif.Tree.Load.children(json, self, self.tree);
  894. delete self.$loading;
  895. self.state.loaded = true;
  896. self.removeType('loader');
  897. Mif.Tree.Draw.update(self);
  898. self.fireEvent('load');
  899. self.tree.fireEvent('loadNode', self);
  900. return self;
  901. }
  902. options=Object.append(Object.append(Object.append({
  903. isSuccess: Function.from(true),
  904. secure: true,
  905. onSuccess: success,
  906. onError: errorFunc,
  907. method: 'get'
  908. }, this.tree.loadOptions(this)), this.loadOptions), options);
  909. function errorFunc(text,error) {
  910. alert(text);
  911. alert(error);
  912. return false;
  913. }
  914. if(options.json) return success(options.json);
  915. new Request.JSON(options).send();
  916. return this;
  917. }
  918. });
  919. /*
  920. ---
  921. name: Mif.Tree.KeyNav
  922. description: Mif.Tree.KeyNav
  923. license: MIT-Style License (http://mifjs.net/license.txt)
  924. copyright: Anton Samoylov (http://mifjs.net)
  925. authors: Anton Samoylov (http://mifjs.net)
  926. requires: Mif.Tree
  927. provides: Mif.Tree.KeyNav
  928. ...
  929. */
  930. Mif.Tree.KeyNav=new Class({
  931. initialize: function(tree){
  932. this.tree = tree;
  933. this.bound = {
  934. action: this.action.bind(this),
  935. attach: this.attach.bind(this),
  936. detach: this.detach.bind(this)
  937. };
  938. tree.addEvents({
  939. 'focus': this.bound.attach,
  940. 'blur': this.bound.detach
  941. });
  942. },
  943. attach: function(){
  944. var event = Browser.ie || Browser.chrome ? 'keydown' : 'keypress';
  945. document.addEvent(event, this.bound.action);
  946. },
  947. detach: function(){
  948. var event = Browser.ie || Browser.chrome ? 'keydown' : 'keypress';
  949. document.removeEvent(event, this.bound.action);
  950. },
  951. action: function(event){
  952. if(!['down','left','right','up', 'pgup', 'pgdown', 'end', 'home'].contains(event.key)) return;
  953. var tree = this.tree;
  954. if(!tree.selected){
  955. tree.select(tree.forest ? tree.root.getFirst() : tree.root);
  956. }else{
  957. var current = tree.selected;
  958. switch (event.key){
  959. case 'down': this.goForward(current); event.stop(); break;
  960. case 'up': this.goBack(current); event.stop(); break;
  961. case 'left': this.goLeft(current); event.stop(); break;
  962. case 'right': this.goRight(current); event.stop(); break;
  963. case 'home': this.goStart(current); event.stop(); break;
  964. case 'end': this.goEnd(current); event.stop(); break;
  965. case 'pgup': this.goPageUp(current); event.stop(); break;
  966. case 'pgdown': this.goPageDown(current); event.stop(); break;
  967. }
  968. }
  969. tree.scrollTo(tree.selected);
  970. },
  971. goForward: function(current){
  972. var forward = current.getNextVisible();
  973. if(forward) this.tree.select(forward);
  974. },
  975. goBack: function(current){
  976. var back = current.getPreviousVisible();
  977. if (back) this.tree.select(back);
  978. },
  979. goLeft: function(current){
  980. if(current.isRoot()){
  981. if(current.isOpen()){
  982. current.toggle();
  983. }else{
  984. return false;
  985. }
  986. }else{
  987. if( current.hasChildren(true) && current.isOpen() ){
  988. current.toggle();
  989. }else{
  990. if(current.tree.forest && current.getParent().isRoot()) return false;
  991. return this.tree.select(current.getParent());
  992. }
  993. }
  994. return true;
  995. },
  996. goRight: function(current){
  997. if(!current.hasChildren(true) && !current.loadable){
  998. return false;
  999. }else if(!current.isOpen()){
  1000. return current.toggle();
  1001. }else{
  1002. return this.tree.select(current.getFirst(true));
  1003. }
  1004. },
  1005. goStart: function(){
  1006. this.tree.select(this.tree.$index[0]);
  1007. },
  1008. goEnd: function(){
  1009. this.tree.select(this.tree.$index.getLast());
  1010. },
  1011. goPageDown: function(current){
  1012. var tree = this.tree;
  1013. var count = (tree.container.clientHeight/tree.height).toInt() - 1;
  1014. var newIndex = Math.min(tree.$index.indexOf(current) + count, tree.$index.length - 1);
  1015. tree.select(tree.$index[newIndex]);
  1016. },
  1017. goPageUp: function(current){
  1018. var tree = this.tree;
  1019. var count = (tree.container.clientHeight/tree.height).toInt() - 1;
  1020. var newIndex = Math.max(tree.$index.indexOf(current) - count, 0);
  1021. tree.select(tree.$index[newIndex]);
  1022. }
  1023. });
  1024. /*
  1025. Event.Keys.extend({
  1026. 'pgdown': 34,
  1027. 'pgup': 33,
  1028. 'home': 36,
  1029. 'end': 35
  1030. });
  1031. */
  1032. /*
  1033. ---
  1034. name: Mif.Tree.Sort
  1035. description: Mif.Tree.Sort
  1036. license: MIT-Style License (http://mifjs.net/license.txt)
  1037. copyright: Anton Samoylov (http://mifjs.net)
  1038. authors: Anton Samoylov (http://mifjs.net)
  1039. requires: Mif.Tree
  1040. provides: Mif.Tree.Sort
  1041. ...
  1042. */
  1043. Mif.Tree.implement({
  1044. initSortable: function(sortFunction){
  1045. this.sortable = true;
  1046. this.sortFunction = sortFunction||function(node1, node2){
  1047. if(node1.name > node2.name){
  1048. return 1;
  1049. }else if(node1.name < node2.name){
  1050. return -1;
  1051. }else{
  1052. return 0;
  1053. }
  1054. };
  1055. this.addEvent('loadChildren', function(parent){
  1056. if(parent) parent.sort();
  1057. });
  1058. this.addEvent('structureChange', function(from, to, where, type){
  1059. from.sort();
  1060. });
  1061. return this;
  1062. }
  1063. });
  1064. Mif.Tree.Node.implement({
  1065. sort: function(sortFunction){
  1066. this.children.sort(sortFunction||this.tree.sortFunction);
  1067. return this;
  1068. }
  1069. });
  1070. /*
  1071. ---
  1072. name: Mif.Tree.Transform
  1073. description: implement move/copy/del/add actions
  1074. license: MIT-Style License (http://mifjs.net/license.txt)
  1075. copyright: Anton Samoylov (http://mifjs.net)
  1076. authors: Anton Samoylov (http://mifjs.net)
  1077. requires: Mif.Tree
  1078. provides: Mif.Tree.Transform
  1079. ...
  1080. */
  1081. Mif.Tree.Node.implement({
  1082. inject: function(node, where, element){//element - internal property
  1083. where = where||'inside';
  1084. var parent = this.parentNode;
  1085. function getPreviousVisible(node){
  1086. var previous = node;
  1087. while(previous){
  1088. previous = previous.getPrevious();
  1089. if(!previous) return null;
  1090. if(!previous.hidden) return previous;
  1091. }
  1092. return null;
  1093. }
  1094. var previousVisible = getPreviousVisible(this);
  1095. var type = element ? 'copy' : 'move';
  1096. switch(where){
  1097. case 'after':
  1098. case 'before':
  1099. if( node['get' + (where == 'after' ? 'Next' : 'Previous')]() == this ) return false;
  1100. if(this.parentNode) {
  1101. this.parentNode.children.erase(this);
  1102. }
  1103. this.parentNode = node.parentNode;
  1104. this.parentNode.children.inject(this, node, where);
  1105. break;
  1106. case 'inside':
  1107. if( node.tree && node.getLast() == this ) return false;
  1108. if(this.parentNode) {
  1109. this.parentNode.children.erase(this);
  1110. }
  1111. if(node.tree){
  1112. if(!node.hasChildren()){
  1113. node.$draw = true;
  1114. node.state.open = true;
  1115. }
  1116. node.children.push(this);
  1117. this.parentNode = node;
  1118. }else{
  1119. node.root = this;
  1120. this.parentNode = null;
  1121. node.fireEvent('drawRoot');
  1122. }
  1123. break;
  1124. }
  1125. var tree = node.tree || node;
  1126. if(this == this.tree.root){
  1127. this.tree.root = false;
  1128. }
  1129. if(this.tree != tree){
  1130. var oldTree = this.tree;
  1131. this.recursive(function(){
  1132. this.tree = tree;
  1133. });
  1134. };
  1135. tree.fireEvent('structureChange', [this, node, where, type]);
  1136. tree.$getIndex();
  1137. if(oldTree) oldTree.$getIndex();
  1138. Mif.Tree.Draw.inject(this, element);
  1139. [node, this, parent, previousVisible, getPreviousVisible(this)].each(function(node){
  1140. Mif.Tree.Draw.update(node);
  1141. });
  1142. return this;
  1143. },
  1144. copy: function(node, where){
  1145. if (this.copyDenied) return this;
  1146. function copy(structure){
  1147. var node = structure.node;
  1148. var tree = structure.tree;
  1149. var options = Object.clone({
  1150. property: node.property,
  1151. type: node.type,
  1152. state: node.state,
  1153. data: node.data
  1154. });
  1155. options.state.open = false;
  1156. var nodeCopy = new Mif.Tree.Node({
  1157. parentNode: structure.parentNode,
  1158. children: [],
  1159. tree: tree
  1160. }, options);
  1161. node.children.each(function(child){
  1162. var childCopy = copy({
  1163. node: child,
  1164. parentNode: nodeCopy,
  1165. tree: tree
  1166. });
  1167. nodeCopy.children.push(childCopy);
  1168. });
  1169. return nodeCopy;
  1170. };
  1171. var nodeCopy = copy({
  1172. node: this,
  1173. parentNode: null,
  1174. tree: node.tree
  1175. });
  1176. return nodeCopy.inject(node, where, Mif.Tree.Draw.node(nodeCopy));
  1177. },
  1178. remove: function(){
  1179. if (this.removeDenied) return;
  1180. this.tree.fireEvent('remove', [this]);
  1181. var parent = this.parentNode, previousVisible = this.getPreviousVisible();
  1182. if(parent) {
  1183. parent.children.erase(this);
  1184. }else if(!this.tree.forest){
  1185. this.tree.root = null;
  1186. }
  1187. this.tree.selected = false;
  1188. this.getDOM('node').destroy();
  1189. this.tree.$getIndex();
  1190. Mif.Tree.Draw.update(parent);
  1191. Mif.Tree.Draw.update(previousVisible);
  1192. this.recursive(function(){
  1193. if(this.id) delete Mif.ids[this.id];
  1194. });
  1195. this.tree.mouse.node = false;
  1196. this.tree.updateHover();
  1197. }
  1198. });
  1199. Mif.Tree.implement({
  1200. move: function(from, to, where){
  1201. if(from.inject(to, where)){
  1202. this.fireEvent('move', [from, to, where]);
  1203. }
  1204. return this;
  1205. },
  1206. copy: function(from, to, where){
  1207. var copy = from.copy(to, where);
  1208. if(copy){
  1209. this.fireEvent('copy', [from, to, where, copy]);
  1210. }
  1211. return this;
  1212. },
  1213. remove: function(node){
  1214. node.remove();
  1215. return this;
  1216. },
  1217. add: function(node, current, where){
  1218. if(!(node instanceof Mif.Tree.Node)){
  1219. node = new Mif.Tree.Node({
  1220. parentNode: null,
  1221. tree: this
  1222. }, node);
  1223. };
  1224. node.inject(current, where, Mif.Tree.Draw.node(node));
  1225. this.fireEvent('add', [node, current, where]);
  1226. return this;
  1227. }
  1228. });
  1229. /*
  1230. ---
  1231. name: Mif.Tree.Drag
  1232. description: implements drag and drop
  1233. license: MIT-Style License (http://mifjs.net/license.txt)
  1234. copyright: Anton Samoylov (http://mifjs.net)
  1235. authors: Anton Samoylov (http://mifjs.net)
  1236. requires: [Mif.Tree, Mif.Tree.Transform, more:/Drag.Move]
  1237. provides: Mif.Tree.Drag
  1238. ...
  1239. */
  1240. Mif.Tree.Drag = new Class({
  1241. Implements: [Events, Options],
  1242. Extends: Drag,
  1243. options:{
  1244. group: 'tree',
  1245. droppables: [],
  1246. snap: 4,
  1247. animate: true,
  1248. open: 600,//time to open node
  1249. scrollDelay: 100,
  1250. scrollSpeed: 100,
  1251. modifier: 'control',//copy
  1252. startPlace: ['icon', 'name'],
  1253. allowContainerDrop: true
  1254. },
  1255. initialize: function(tree, options){
  1256. tree.drag = this;
  1257. this.setOptions(options);
  1258. Object.append(this, {
  1259. tree: tree,
  1260. snap: this.options.snap,
  1261. groups: [],
  1262. droppables: [],
  1263. action: this.options.action
  1264. });
  1265. this.addToGroups(this.options.group);
  1266. this.setDroppables(this.options.droppables);
  1267. Object.append(tree.defaults, {
  1268. dropDenied: [],
  1269. dragDisabled: false
  1270. });
  1271. tree.addEvent('drawRoot',function(){
  1272. tree.root.dropDenied.combine(['before', 'after']);
  1273. });
  1274. this.pointer = new Element('div').addClass('mif-tree-pointer').inject(tree.wrapper,'bottom');
  1275. this.current = Mif.Tree.Drag.current;
  1276. this.target = Mif.Tree.Drag.target;
  1277. this.where = Mif.Tree.Drag.where;
  1278. this.element = [this.current, this.target, this.where];
  1279. this.document = tree.wrapper.getDocument();
  1280. this.selection = (Browser.ie) ? 'selectstart' : 'mousedown';
  1281. this.bound = {
  1282. start: this.start.bind(this),
  1283. check: this.check.bind(this),
  1284. drag: this.drag.bind(this),
  1285. stop: this.stop.bind(this),
  1286. cancel: this.cancel.bind(this),
  1287. eventStop: Function.from(false),
  1288. leave: this.leave.bind(this),
  1289. enter: this.enter.bind(this),
  1290. keydown: this.keydown.bind(this)
  1291. };
  1292. this.attach();
  1293. this.addEvent('start', function(){
  1294. Mif.Tree.Drag.dropZone=this;
  1295. this.tree.unselect();
  1296. document.addEvent('keydown', this.bound.keydown);
  1297. this.setDroppables();
  1298. this.droppables.each(function(item){
  1299. item.getElement().addEvents({mouseleave: this.bound.leave, mouseenter: this.bound.enter});
  1300. }, this);
  1301. Mif.Tree.Drag.current.getDOM('name').addClass('mif-tree-drag-current');
  1302. this.addGhost();
  1303. }, true);
  1304. this.addEvent('complete', function(){
  1305. document.removeEvent('keydown', this.bound.keydown);
  1306. this.droppables.each(function(item){
  1307. item.getElement().removeEvent('mouseleave', this.bound.leave).removeEvent('mouseenter', this.bound.enter);
  1308. }, this);
  1309. Mif.Tree.Drag.current.getDOM('name').removeClass('mif-tree-drag-current');
  1310. var dropZone = Mif.Tree.Drag.dropZone;
  1311. if(!dropZone || dropZone.where=='notAllowed'){
  1312. Mif.Tree.Drag.startZone.onstop();
  1313. Mif.Tree.Drag.startZone.emptydrop();
  1314. return;
  1315. }
  1316. if(dropZone.onstop) dropZone.onstop();
  1317. dropZone.beforeDrop();
  1318. });
  1319. },
  1320. getElement: function(){
  1321. return this.tree.wrapper;
  1322. },
  1323. addToGroups: function(groups){
  1324. groups = Array.from(groups);
  1325. this.groups.combine(groups);
  1326. groups.each(function(group){
  1327. Mif.Tree.Drag.groups[group]=(Mif.Tree.Drag.groups[group]||[]).include(this);
  1328. }, this);
  1329. },
  1330. setDroppables: function(droppables){
  1331. this.droppables.combine(Array.from(droppables));
  1332. this.groups.each(function(group){
  1333. this.droppables.combine(Mif.Tree.Drag.groups[group]);
  1334. }, this);
  1335. },
  1336. attach: function(){
  1337. this.tree.wrapper.addEvent('mousedown', this.bound.start);
  1338. return this;
  1339. },
  1340. detach: function(){
  1341. this.tree.wrapper.removeEvent('mousedown', this.bound.start);
  1342. return this;
  1343. },
  1344. dragTargetSelect: function(){
  1345. function addDragTarget(){
  1346. this.current.getDOM('name').addClass('mif-tree-drag-current');
  1347. }
  1348. function removeDragTarget(){
  1349. this.current.getDOM('name').removeClass('mif-tree-drag-current');
  1350. }
  1351. this.addEvent('start',addDragTarget.bind(this));
  1352. this.addEvent('beforeComplete',removeDragTarget.bind(this));
  1353. },
  1354. leave: function(event){
  1355. var dropZone = Mif.Tree.Drag.dropZone;
  1356. if(dropZone){
  1357. dropZone.where = 'notAllowed';
  1358. Mif.Tree.Drag.ghost.firstChild.className = 'mif-tree-ghost-icon mif-tree-ghost-'+dropZone.where;
  1359. if(dropZone.onleave) dropZone.onleave();
  1360. Mif.Tree.Drag.dropZone = false;
  1361. }
  1362. var relatedZone = this.getZone(event.relatedTarget);
  1363. if(relatedZone) this.enter(null, relatedZone);
  1364. },
  1365. onleave: function(){
  1366. this.tree.unselect();
  1367. this.clean();
  1368. clearInterval(this.scrolling);
  1369. this.scrolling = null;
  1370. this.target = false;
  1371. },
  1372. enter: function(event, zone){
  1373. if(event) zone = this.getZone(event.target);
  1374. var dropZone = Mif.Tree.Drag.dropZone;
  1375. if(dropZone && dropZone.onleave) dropZone.onleave();
  1376. Mif.Tree.Drag.dropZone = zone;
  1377. zone.current = Mif.Tree.Drag.current;
  1378. if(zone.onenter) zone.onenter();
  1379. },
  1380. onenter: function(){
  1381. this.onleave();
  1382. },
  1383. getZone: function(target){//private leave/enter
  1384. if(!target) return false;
  1385. var parent = $(target);
  1386. do{
  1387. for(var l = this.droppables.length;l--;){
  1388. var zone = this.droppables[l];
  1389. if( parent == zone.getElement() ) {
  1390. return zone;
  1391. }
  1392. }
  1393. parent = parent.getParent();
  1394. }while(parent);
  1395. return false;
  1396. },
  1397. keydown: function(event){
  1398. if(event.key == 'esc') {
  1399. var zone = Mif.Tree.Drag.dropZone;
  1400. if(zone) zone.where = 'notAllowed';
  1401. this.stop(event);
  1402. }
  1403. },
  1404. autoScroll: function(){
  1405. var y = this.y;
  1406. if(y == -1) return;
  1407. var wrapper = this.tree.wrapper;
  1408. var top = y-wrapper.scrollTop;
  1409. var bottom = wrapper.offsetHeight-top;
  1410. var sign = 0;
  1411. var delta;
  1412. if(top < this.tree.height){
  1413. delta = top;
  1414. sign = 1;
  1415. }else if(bottom < this.tree.height){
  1416. delta = bottom;
  1417. sign = -1;
  1418. }
  1419. if(sign && !this.scrolling){
  1420. this.scrolling = function(node){
  1421. if(y != this.y){
  1422. y = this.y;
  1423. delta = (sign == 1 ? (y - wrapper.scrollTop) : (wrapper.offsetHeight - y + wrapper.scrollTop)) || 1;
  1424. }
  1425. wrapper.scrollTop = wrapper.scrollTop - sign*this.options.scrollSpeed/delta;
  1426. }.periodical(this.options.scrollDelay, this, [sign]);
  1427. }
  1428. if(!sign){
  1429. clearInterval(this.scrolling);
  1430. this.scrolling = null;
  1431. }
  1432. },
  1433. start: function(event){//mousedown
  1434. if(event.rightClick) return;
  1435. if (this.options.preventDefault) event.preventDefault();
  1436. this.fireEvent('beforeStart', this.element);
  1437. var target = this.tree.mouse.target;
  1438. if(!target) return;
  1439. this.current = Array.from(this.options.startPlace).contains(target) ? this.tree.mouse.node : false;
  1440. if(!this.current || this.current.dragDisabled) {
  1441. return;
  1442. }
  1443. Mif.Tree.Drag.current = this.current;
  1444. Mif.Tree.Drag.startZone = this;
  1445. this.mouse = {start: event.page};
  1446. this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
  1447. this.document.addEvent(this.selection, this.bound.eventStop);
  1448. },
  1449. drag: function(event){
  1450. Mif.Tree.Drag.ghost.position({x:event.page.x+20,y:event.page.y+20});
  1451. var dropZone = Mif.Tree.Drag.dropZone;
  1452. if(!dropZone||!dropZone.ondrag) return;
  1453. Mif.Tree.Drag.dropZone.ondrag(event);
  1454. },
  1455. ondrag: function(event){
  1456. this.autoScroll();
  1457. if(!this.checkTarget()) return;
  1458. this.clean();
  1459. var where = this.where;
  1460. var target = this.target;
  1461. var ghostType = where;
  1462. if(where == 'after' && target && (target.getNext()) || where == 'before' && target.getPrevious()){
  1463. ghostType = 'between';
  1464. }
  1465. Mif.Tree.Drag.ghost.firstChild.className = 'mif-tree-ghost-icon mif-tree-ghost-' + ghostType;
  1466. if(where == 'notAllowed'){
  1467. this.tree.unselect();
  1468. return;
  1469. }
  1470. if(target && target.tree) this.tree.select(target);
  1471. if(where == 'inside'){
  1472. if(target.tree && !target.isOpen() && !this.openTimer && (target.loadable || target.hasChildren()) ){
  1473. this.wrapper = target.getDOM('wrapper').setStyle('cursor', 'progress');
  1474. this.openTimer = function(){
  1475. target.toggle();
  1476. this.clean();
  1477. }.delay(this.options.open,this);
  1478. }
  1479. }else{
  1480. var wrapper = this.tree.wrapper;
  1481. var top = this.index*this.tree.height;
  1482. if(where == 'after') top += this.tree.height;
  1483. this.pointer.setStyles({
  1484. left: wrapper.scrollLeft,
  1485. top: top,
  1486. width: wrapper.clientWidth
  1487. });
  1488. }
  1489. },
  1490. clean: function(){
  1491. this.pointer.style.width = 0;
  1492. if(this.openTimer){
  1493. clearInterval(this.openTimer);
  1494. this.openTimer = false;
  1495. this.wrapper.style.cursor = 'inherit';
  1496. this.wrapper = false;
  1497. }
  1498. },
  1499. addGhost: function(){
  1500. var wrapper = this.current.getDOM('wrapper');
  1501. var ghost = new Element('span').addClass('mif-tree-ghost');
  1502. ghost.adopt(Mif.Tree.Draw.node(this.current).getFirst())
  1503. .inject(document.body,'bottom').addClass('mif-tree-ghost-notAllowed').setStyle('position', 'absolute');
  1504. new Element('span').set('html',Mif.Tree.Draw.zeroSpace).inject(ghost,'top');
  1505. ghost.getLast().getFirst().className = '';
  1506. Mif.Tree.Drag.ghost = ghost;
  1507. },
  1508. checkTarget: function(){
  1509. this.y = this.tree.mouse.coords.y;
  1510. var target = this.tree.mouse.node;
  1511. if(!target){
  1512. if(this.options.allowContainerDrop && (this.tree.forest || !this.tree.root)){
  1513. this.target = this.tree.$index.getLast();
  1514. this.index = this.tree.$index.length-1;
  1515. if(this.index == -1){
  1516. this.where = 'inside';
  1517. this.target = this.tree.root || this.tree;
  1518. }else{
  1519. this.where = 'after';
  1520. }
  1521. }else{
  1522. this.target = false;
  1523. this.where = 'notAllowed';
  1524. }
  1525. this.fireEvent('drag');
  1526. return true;
  1527. };
  1528. if((this.current instanceof Mif.Tree.Node) && this.current.contains(target)){
  1529. this.target = target;
  1530. this.where = 'notAllowed';
  1531. this.fireEvent('drag');
  1532. return true;
  1533. };
  1534. this.index = Math.floor(this.y/this.tree.height);
  1535. var delta = this.y - this.index*this.tree.height;
  1536. var deny = target.dropDenied;
  1537. if(this.tree.sortable){
  1538. deny.include('before').include('after');
  1539. };
  1540. var where;
  1541. if(!deny.contains('inside') && delta > (this.tree.height/4) && delta < (3/4*this.tree.height)){
  1542. where = 'inside';
  1543. }else{
  1544. if(delta < this.tree.height/2){
  1545. if(deny.contains('before')){
  1546. if(deny.contains('inside')){
  1547. where = deny.contains('after') ? 'notAllowed' : 'after';
  1548. }else{
  1549. where = 'inside';
  1550. }
  1551. }else{
  1552. where = 'before';
  1553. }
  1554. }else{
  1555. if(deny.contains('after')){
  1556. if(deny.contains('inside')){
  1557. where = deny.contains('before') ? 'notAllowed' : 'before';
  1558. }else{
  1559. where = 'inside';
  1560. }
  1561. }else{
  1562. where = 'after';
  1563. }
  1564. }
  1565. };
  1566. if(this.where == where && this.target == target) return false;
  1567. this.where = where;
  1568. this.target = target;
  1569. this.fireEvent('drag');
  1570. return true;
  1571. },
  1572. emptydrop: function(){
  1573. var current = this.current, target = this.target, where = this.where;
  1574. var scroll = this.tree.scroll;
  1575. var complete = function(){
  1576. scroll.removeEvent('complete', complete);
  1577. if(this.options.animate){
  1578. var wrapper = current.getDOM('wrapper');
  1579. var position = wrapper.getPosition();
  1580. Mif.Tree.Drag.ghost.set('morph',{
  1581. duration: 'short',
  1582. onComplete: function(){
  1583. Mif.Tree.Drag.ghost.dispose();
  1584. this.fireEvent('emptydrop', this.element);
  1585. }.bind(this)
  1586. });
  1587. Mif.Tree.Drag.ghost.morph({left: position.x, top: position.y});
  1588. return;
  1589. };
  1590. Mif.Tree.Drag.ghost.dispose();
  1591. this.fireEvent('emptydrop', this.element);
  1592. return;
  1593. }.bind(this);
  1594. scroll.addEvent('complete', complete);
  1595. this.tree.select(this.current);
  1596. this.tree.scrollTo(this.current);
  1597. },
  1598. beforeDrop: function(){
  1599. if(this.options.beforeDrop){
  1600. this.options.beforeDrop.apply(this, [this.current, this.target, this.where]);
  1601. }else{
  1602. this.drop();
  1603. }
  1604. },
  1605. drop: function(){
  1606. var current = this.current, target = this.target, where = this.where;
  1607. Mif.Tree.Drag.ghost.dispose();
  1608. var action = this.action || (this.tree.key[this.options.modifier] ? 'copy' : 'move');
  1609. if(this.where == 'inside' && target.tree && !target.isOpen()){
  1610. if(target.tree) target.toggle();
  1611. if(target.$loading){
  1612. var onLoad = function(){
  1613. this.tree[action](current, target, where);
  1614. this.tree.select(current).scrollTo(current);
  1615. this.fireEvent('drop', [current, target, where]);
  1616. target.removeEvent('load',onLoad);
  1617. };
  1618. target.addEvent('load',onLoad);
  1619. return;
  1620. };
  1621. };
  1622. if(!(current instanceof Mif.Tree.Node )){
  1623. current = current.toNode(this.tree);
  1624. }
  1625. this.tree[action](current, target, where);
  1626. this.tree.select(current).scrollTo(current);
  1627. this.fireEvent('drop', [current, target, where]);
  1628. },
  1629. onstop: function(){
  1630. this.clean();
  1631. clearInterval(this.scrolling);
  1632. }
  1633. });
  1634. Mif.Tree.Drag.groups={};
  1635. /*
  1636. ---
  1637. name: Mif.Tree.Drag.Element
  1638. description: dom element droppable
  1639. license: MIT-Style License (http://mifjs.net/license.txt)
  1640. copyright: Anton Samoylov (http://mifjs.net)
  1641. authors: Anton Samoylov (http://mifjs.net)
  1642. requires: Mif.Tree.Drag
  1643. provides: Mif.Tree.Drag.Element
  1644. ...
  1645. */
  1646. Mif.Tree.Drag.Element=new Class({
  1647. Implements: [Options, Events],
  1648. initialize: function(element, options){
  1649. this.element=$(element);
  1650. this.setOptions(options);
  1651. },
  1652. getElement: function(){
  1653. return this.element;
  1654. },
  1655. onleave: function(){
  1656. this.where='notAllowed';
  1657. Mif.Tree.Drag.ghost.firstChild.className='mif-tree-ghost-icon mif-tree-ghost-'+this.where;
  1658. },
  1659. onenter: function(){
  1660. this.where='inside';
  1661. Mif.Tree.Drag.ghost.firstChild.className='mif-tree-ghost-icon mif-tree-ghost-'+this.where;
  1662. },
  1663. beforeDrop: function(){
  1664. if(this.options.beforeDrop){
  1665. this.options.beforeDrop.apply(this, [this.current, this.trarget, this.where]);
  1666. }else{
  1667. this.drop();
  1668. }
  1669. },
  1670. drop: function(){
  1671. Mif.Tree.Drag.ghost.dispose();
  1672. this.fireEvent('drop', Mif.Tree.Drag.current);
  1673. }
  1674. });
  1675. /*
  1676. ---
  1677. name: Mif.Tree.Rename
  1678. description: Mif.Tree.Rename
  1679. license: MIT-Style License (http://mifjs.net/license.txt)
  1680. copyright: Anton Samoylov (http://mifjs.net)
  1681. authors: Anton Samoylov (http://mifjs.net)
  1682. requires: Mif.Tree
  1683. provides: Mif.Tree.Rename
  1684. ...
  1685. */
  1686. Mif.Tree.implement({
  1687. attachRenameEvents: function(){
  1688. this.wrapper.addEvents({
  1689. click: function(event){
  1690. if($(event.target).get('tag') == 'input') return;
  1691. this.beforeRenameComplete();
  1692. }.bind(this),
  1693. keydown: function(event){
  1694. if(event.key == 'enter'){
  1695. this.beforeRenameComplete();
  1696. }
  1697. if(event.key == 'esc'){
  1698. this.renameCancel();
  1699. }
  1700. }.bind(this)
  1701. });
  1702. },
  1703. disableEvents: function(){
  1704. if(!this.eventStorage) this.eventStorage = new Element('div');
  1705. this.eventStorage.cloneEvents(this.wrapper);
  1706. this.wrapper.removeEvents();
  1707. },
  1708. enableEvents: function(){
  1709. this.wrapper.removeEvents();
  1710. this.wrapper.cloneEvents(this.eventStorage);
  1711. },
  1712. getInput: function(){
  1713. if(!this.input){
  1714. this.input = new Element('input').addClass('mif-tree-rename');
  1715. this.input.addEvent('focus',function(){this.select();}).addEvent('click', function(event) {
  1716. event.stop();
  1717. });
  1718. Mif.Tree.Rename.autoExpand(this.input);
  1719. }
  1720. return this.input;
  1721. },
  1722. startRename: function(node){
  1723. this.focus();
  1724. this.unselect();
  1725. this.disableEvents();
  1726. this.attachRenameEvents();
  1727. var input = this.getInput();
  1728. input.value = node.name;
  1729. this.renameName = node.getDOM('name');
  1730. this.renameNode = node;
  1731. input.setStyle('width', this.renameName.offsetWidth+15);
  1732. input.replaces(this.renameName);
  1733. input.focus();
  1734. },
  1735. finishRename: function(){
  1736. this.renameName.replaces(this.getInput());
  1737. },
  1738. beforeRenameComplete: function(){
  1739. if(this.options.beforeRename){
  1740. var newName = this.getInput().value;
  1741. var node = this.renameNode;
  1742. this.options.beforeRename.apply(this, [node, node.name, newName]);
  1743. }else{
  1744. this.renameComplete();
  1745. }
  1746. },
  1747. renameComplete: function(){
  1748. this.enableEvents();
  1749. this.finishRename();
  1750. var node = this.renameNode;
  1751. var oldName = node.name;
  1752. node.set({
  1753. property:{
  1754. name: this.getInput().value
  1755. }
  1756. });
  1757. this.fireEvent('rename', [node, node.name, oldName]);
  1758. this.select(node);
  1759. },
  1760. renameCancel: function(){
  1761. this.enableEvents();
  1762. this.finishRename();
  1763. this.select(this.renameNode);
  1764. }
  1765. });
  1766. Mif.Tree.Node.implement({
  1767. rename: function(){
  1768. if (this.property.renameDenied) return;
  1769. this.tree.startRename(this);
  1770. }
  1771. });
  1772. Mif.Tree.Rename={
  1773. autoExpand: function(input){
  1774. var span = new Element('span').addClass('mif-tree-rename').setStyles({
  1775. position: 'absolute',
  1776. left: -2000,
  1777. top:0,
  1778. padding: 0
  1779. }).inject(document.body,'bottom');
  1780. input.addEvent('keydown',function(event){
  1781. (function(){
  1782. input.setStyle('width',Math.max(20, span.set('html', input.value.replace(/\s/g,'&nbsp;')).offsetWidth+15));
  1783. }).delay(10);
  1784. });
  1785. }
  1786. };
  1787. /*
  1788. ---
  1789. name: Mif.Tree.Checkbox
  1790. description: Mif.Tree.Checkbox
  1791. license: MIT-Style License (http://mifjs.net/license.txt)
  1792. copyright: Anton Samoylov (http://mifjs.net)
  1793. authors: Anton Samoylov (http://mifjs.net)
  1794. requires: Mif.Tree
  1795. provides: Mif.Tree.Checkbox
  1796. ...
  1797. */
  1798. Mif.Tree.implement({
  1799. initCheckbox: function(type){
  1800. this.checkboxType = type || 'simple';
  1801. this.dfltState.checked = 'unchecked';
  1802. this.defaults.hasCheckbox = true;
  1803. this.wrapper.addEvent('click', this.checkboxClick.bind(this));
  1804. if(this.checkboxType == 'simple') return;
  1805. this.addEvent('loadChildren', function(node){
  1806. if(!node) return;
  1807. if(node.state.checked == 'checked'){
  1808. node.recursive(function(){
  1809. this.state.checked = 'checked';
  1810. });
  1811. }else{
  1812. node.getFirst().setParentCheckbox(1);
  1813. }
  1814. });
  1815. },
  1816. checkboxClick: function(event){
  1817. if(this.mouse.target!='checkbox') {return;}
  1818. this.mouse.node['switch']();
  1819. },
  1820. getChecked: function(includePartially){
  1821. var checked=[];
  1822. this.root.recursive(function(){
  1823. var condition = includePartially ? this.state.checked!=='unchecked' : this.state.checked=='checked';
  1824. if(this.hasCheckbox && condition) checked.push(this);
  1825. });
  1826. return checked;
  1827. }
  1828. });
  1829. Mif.Tree.Node.implement({
  1830. 'switch' : function(state){
  1831. if(this.state.checked == state || !this.hasCheckbox) return this;
  1832. var type = this.tree.checkboxType;
  1833. var checked=(this.state.checked == 'checked') ? 'unchecked' : 'checked';
  1834. if(type == 'simple'){
  1835. this.setCheckboxState(checked);
  1836. this.tree.fireEvent(checked == 'checked' ? 'check' : 'unCheck', this);
  1837. this.tree.fireEvent('switch', [this, (checked == 'checked' ? true : false)]);
  1838. return this;
  1839. };
  1840. this.recursive(function(){
  1841. this.setCheckboxState(checked);
  1842. });
  1843. this.setParentCheckbox();
  1844. this.tree.fireEvent(checked == 'checked' ? 'check' : 'unCheck', this);
  1845. this.tree.fireEvent('switch', [this, (checked == 'checked' ? true : false)]);
  1846. return this;
  1847. },
  1848. setCheckboxState: function(state){
  1849. if(!this.hasCheckbox) return;
  1850. var oldState = this.state.checked;
  1851. this.state.checked = state;
  1852. if((!this.parentNode&&this.tree.$draw) || (this.parentNode && this.parentNode.$draw)){
  1853. this.getDOM('checkbox').removeClass('mif-tree-node-'+oldState).addClass('mif-tree-node-'+state);
  1854. }
  1855. },
  1856. setParentCheckbox: function(s){
  1857. if(!this.hasCheckbox || !this.parentNode || (this.tree.forest && !this.parentNode.parentNode)) return;
  1858. var parent = this.parentNode;
  1859. var state = '';
  1860. var children = parent.children;
  1861. for(var i = children.length; i--; i>0){
  1862. var child = children[i];
  1863. if(!child.hasCheckbox) continue;
  1864. var childState = child.state.checked;
  1865. if(childState == 'partially'){
  1866. state = 'partially';
  1867. break;
  1868. }else if(childState == 'checked'){
  1869. if(state == 'unchecked'){
  1870. state = 'partially';
  1871. break;
  1872. }
  1873. state = 'checked';
  1874. }else{
  1875. if(state == 'checked'){
  1876. state = 'partially';
  1877. break;
  1878. }else{
  1879. state = 'unchecked';
  1880. }
  1881. }
  1882. }
  1883. if(parent.state.checked == state || (s && state == 'partially' && parent.state.checked == 'checked')){return;};
  1884. parent.setCheckboxState(state);
  1885. parent.setParentCheckbox(s);
  1886. }
  1887. });
  1888. /*
  1889. ---
  1890. name: Mif.Tree.CookieStorage
  1891. description: Mif.Tree.Node
  1892. license: MIT-Style License (http://mifjs.net/license.txt)
  1893. copyright: Anton Samoylov (http://mifjs.net)
  1894. authors: Anton Samoylov (http://mifjs.net)
  1895. requires: Mif.Tree
  1896. provides: Mif.Tree.CookieStorage
  1897. ...
  1898. */
  1899. Mif.Tree.CookieStorage = new Class({
  1900. Implements: [Options],
  1901. options:{
  1902. store: function(node){
  1903. return node.property.id;
  1904. },
  1905. retrieve: function(value){
  1906. return Mif.id(value);
  1907. },
  1908. event: 'toggle',
  1909. action: 'toggle'
  1910. },
  1911. initialize: function(tree, options){
  1912. this.setOptions(options);
  1913. this.tree = tree;
  1914. this.cookie = new Cookie('mif.tree:' + this.options.event + tree.id||'');
  1915. this.nodes = [];
  1916. this.initSave();
  1917. },
  1918. write: function(){
  1919. this.cookie.write(JSON.encode(this.nodes));
  1920. },
  1921. read: function(){
  1922. return JSON.decode(this.cookie.read()) || [];
  1923. },
  1924. restore: function(data){
  1925. if(!data){
  1926. this.restored = this.restored || this.read();
  1927. }
  1928. var restored = data || this.restored;
  1929. for(var i = 0, l = restored.length; i < l; i++){
  1930. var stored = restored[i];
  1931. var node = this.options.retrieve(stored);
  1932. if(node){
  1933. node[this.options.action](true);
  1934. restored.erase(stored);
  1935. l--;
  1936. }
  1937. }
  1938. return restored;
  1939. },
  1940. initSave: function(){
  1941. this.tree.addEvent(this.options.event, function(node, state){
  1942. var value = this.options.store(node);
  1943. if(state){
  1944. this.nodes.include(value);
  1945. }else{
  1946. this.nodes.erase(value);
  1947. }
  1948. this.write();
  1949. }.bind(this));
  1950. }
  1951. });