1
0

mif.tree-v1.2.6.4.js 56 KB

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