Source of js/Ext.ux.FileTreePanel.js:
  1. // vim: ts=4:sw=4:nu:fdc=4:nospell
  2. /**
  3. * Ext.ux.FileTreePanel
  4. *
  5. * @author Ing. Jozef Sakáloš
  6. * @version $Id: Ext.ux.FileTreePanel.js 266 2008-05-18 23:24:47Z jozo $
  7. * @date 13. March 2008
  8. *
  9. * @license Ext.ux.FileTreePanel is licensed under the terms of
  10. * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
  11. * that the code/component(s) do NOT become part of another Open Source or Commercially
  12. * licensed development library or toolkit without explicit permission.
  13. *
  14. * License details: http://www.gnu.org/licenses/lgpl.html
  15. */
  16.  
  17. /*global Ext, window, document, setTimeout */
  18.  
  19. /**
  20. * @class Ext.ux.FileTreePanel
  21. * @extends Ext.tree.TreePanel
  22. */
  23.  
  24. Ext.ux.FileTreePanel = Ext.extend(Ext.tree.TreePanel, {
  25. // config variables overridable from outside
  26. // {{{
  27. /**
  28. * @cfg {Object} baseParams This object is not used directly by FileTreePanel but it is
  29. * propagated to lower level objects instead. Included here for convenience.
  30. */
  31.  
  32. /**
  33. * @cfg {String} confirmText Text to display as title of confirmation message box
  34. */
  35. confirmText:'Confirm'
  36.  
  37. /**
  38. * @cfg {Boolean} containerScroll true to register
  39. * this container with ScrollManager (defaults to true)
  40. */
  41. ,containerScroll:true
  42.  
  43. /**
  44. * @cfg {String} deleteText Delete text (for message box title or other displayed texts)
  45. */
  46. ,deleteText:'Delete'
  47.  
  48. /**
  49. * @cfg {String} deleteUrl URL to use when deleting; this.url is used if not set (defaults to undefined)
  50. */
  51.  
  52. /**
  53. * @cfg {String} downloadUrl URL to use when downloading; this.url is used if not set (defaults to undefined)
  54. */
  55.  
  56. /**
  57. * @cfg {Boolean} enableDD true to enable drag & drop of files and folders (defaults to true)
  58. */
  59. ,enableDD:true
  60.  
  61. /**
  62. * @cfg {Boolean) enableDelete true to enable to delete files and directories.
  63. * If false context menu item is not shown (defaults to true)
  64. */
  65. ,enableDelete:true
  66.  
  67. /**
  68. * @cfg {Boolean) enableNewDir true to enable to create new directory.
  69. * If false context menu item is not shown (defaults to true)
  70. */
  71. ,enableNewDir:true
  72.  
  73. /**
  74. * @cfg {Boolean) enableOpen true to enable open submenu
  75. * If false context menu item is not shown (defaults to true)
  76. */
  77. ,enableOpen:true
  78.  
  79. /**
  80. * @cfg {Boolean} enableProgress true to enable querying server for progress information
  81. * Passed to underlying uploader. Included here for convenience.
  82. */
  83. ,enableProgress:true
  84.  
  85. /**
  86. * @cfg {Boolean) enableRename true to enable to rename files and directories.
  87. * If false context menu item is not shown (defaults to true)
  88. */
  89. ,enableRename:true
  90.  
  91. /**
  92. * @cfg {Boolean} enableSort true to enable sorting of tree. See also folderSort (defaults to true)
  93. */
  94. ,enableSort:true
  95.  
  96. /**
  97. * @cfg {Boolean) enableUpload true to enable to upload files.
  98. * If false context menu item is not shown (defaults to true)
  99. */
  100. ,enableUpload:true
  101.  
  102. /**
  103. * @cfg {String} errorText Text to display for an error
  104. */
  105. ,errorText:'Error'
  106.  
  107. /**
  108. * @cfg {String} existsText Text to display in message box if file exists
  109. */
  110. ,existsText:'File <b>{0}</b> already exists'
  111.  
  112. /**
  113. * @cfg {Boolean} true to expand root node on FileTreePanel render (defaults to true)
  114. */
  115. ,expandOnRender:true
  116.  
  117. /**
  118. * @cfg {String} fileCls class prefix to add to nodes. "-extension" is appended to
  119. * this prefix to form filetype class, for example: file-odt, file-pdf. These classes
  120. * are used to display correct filetype icons in the tree. css file and icons must
  121. * exist of course.
  122. */
  123. ,fileCls:'file'
  124.  
  125. /**
  126. * @cfg {String} fileText
  127. */
  128. ,fileText:'File'
  129.  
  130. /**
  131. * @cfg {Boolean} focusPopup true to focus new browser popup window for 'popup' openMode
  132. * (defaults to true)
  133. */
  134. ,focusPopup:true
  135.  
  136. /**
  137. * @cfg {Boolean} folderSort true to place directories at the top of the tree (defaults to true)
  138. */
  139. ,folderSort:true
  140.  
  141. /**
  142. * @cfg {String} hrefPrefix Text to prepend before file href for file open command.
  143. * (defaults to '')
  144. */
  145. ,hrefPrefix:''
  146.  
  147. /**
  148. * @cfg {String} hrefSuffix Text to append to file href for file open command.
  149. * (defaults to '')
  150. */
  151. ,hrefSuffix:''
  152.  
  153. /**
  154. * @cfg {String} layout Layout to use for this panel (defaults to 'fit')
  155. */
  156. ,layout:'fit'
  157.  
  158. /**
  159. * @cfg {String} loadingText Text to use for load mask msg
  160. */
  161. ,loadingText:'Loading'
  162.  
  163. /**
  164. * @cfg {Boolean} loadMask True to mask tree panel while loading
  165. */
  166. ,loadMask:false
  167.  
  168. /**
  169. * @cfg {Number} maxFileSize Maximum upload file size in bytes
  170. * This config property is propagated down to uploader for convenience
  171. */
  172. ,maxFileSize:524288
  173.  
  174. /**
  175. * @cfg {Number} maxMsgLen Maximum message length for message box (defaults to 2000).
  176. * If message is longer Ext.util.Format.ellipsis is used to truncate it and append ...
  177. */
  178. ,maxMsgLen:2000
  179.  
  180. /**
  181. * @cfg {String} method Method to use when posting to server. Other valid value is 'get'
  182. * (defaults to 'post')
  183. */
  184. ,method:'post'
  185.  
  186. /**
  187. * @cfg {String} newdirText Default name for new directories (defaults to 'New Folder')
  188. */
  189. ,newdirText:'New Folder'
  190.  
  191. /**
  192. * @cfg {String} newdirUrl URL to use when creating new directory;
  193. * this.url is used if not set (defaults to undefined)
  194. */
  195.  
  196. /**
  197. * @cfg {String} openMode Default file open mode. This mode is used when user dblclicks
  198. * a file. Other valid values are '_self', '_blank' and 'download' (defaults to 'popup')
  199. */
  200. ,openMode:'popup'
  201.  
  202. /**
  203. * @cfg {String} overwriteText Text to use in overwrite confirmation message box
  204. */
  205. ,overwriteText:'Do you want to overwrite it?'
  206.  
  207. /**
  208. * @cfg {String} popupFeatures Features for new browser window opened by popup open mode
  209. */
  210. ,popupFeatures:'width=800,height=600,dependent=1,scrollbars=1,resizable=1,toolbar=1'
  211.  
  212. /**
  213. * @cfg {Boolean} readOnly true to disable write operations. treeEditor and context menu
  214. * are not created if true (defaults to false)
  215. */
  216. ,readOnly:false
  217.  
  218. /**
  219. * @cfg {String} reallyWantText Text to display for that question
  220. */
  221. ,reallyWantText:'Do you really want to'
  222.  
  223. /**
  224. * @cfg {String} renameUrl URL to use when renaming; this.url is used if not set (defaults to undefined)
  225. */
  226.  
  227. /**
  228. * @cfg {String} rootPath Relative path pointing to the directory that is root of this tree (defaults to 'root')
  229. */
  230. ,rootPath:'root'
  231.  
  232. /**
  233. * @cfg {String} rootText Text to display for root node (defaults to 'Tree Root')
  234. */
  235. ,rootText:'Tree Root'
  236.  
  237. /**
  238. * @cfg {Boolean} rootVisible true = root node visible, false = hidden (defaults to true)
  239. */
  240. ,rootVisible:true
  241.  
  242. /**
  243. * @cfg {Boolean} selectOnEdit true to select the edited text on edit start (defaults to true)
  244. */
  245. ,selectOnEdit:true
  246.  
  247. /**
  248. * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
  249. * This config property is propagated down to uploader for convenience
  250. */
  251. ,singleUpload:false
  252.  
  253. /**
  254. * @cfg {Boolean} topMenu true to create top toolbar with menu in addition to contextmenu
  255. */
  256. ,topMenu:false
  257.  
  258. /**
  259. * @cfg {String} url URL to use when communicating with server
  260. */
  261. ,url:'filetree.php'
  262. // }}}
  263.  
  264. // overrides
  265. // {{{
  266. /**
  267. * called by Ext when instantiating
  268. * @private
  269. * @param {Object} config Configuration object
  270. */
  271. ,initComponent:function() {
  272.  
  273. // {{{
  274. Ext.apply(this, {
  275.  
  276. // create root node
  277. root:new Ext.tree.AsyncTreeNode({
  278. text:this.rootText
  279. ,path:this.rootPath
  280. ,rootVisible:this.rootVisible
  281. ,allowDrag:false
  282. })
  283.  
  284. // create treeEditor
  285. ,treeEditor:!this.readOnly ? new Ext.tree.TreeEditor(this, {
  286. allowBlank:false
  287. ,cancelOnEsc:true
  288. ,completeOnEnter:true
  289. ,ignoreNoChange:true
  290. ,selectOnFocus:this.selectOnEdit
  291. }) : undefined
  292.  
  293. // drop config
  294. ,dropConfig:this.dropConfig ? this.dropConfig : {
  295. ddGroup:this.ddGroup || 'TreeDD'
  296. ,appendOnly:this.enableSort
  297. ,expandDelay:3600000 // do not expand on drag over node
  298. }
  299.  
  300. // create treeSorter
  301. ,treeSorter:this.enableSort ? new Ext.tree.TreeSorter(this, {folderSort:this.folderSort}) : undefined
  302.  
  303. // {{{
  304. ,keys:[{
  305. // Enter = open
  306. key:Ext.EventObject.ENTER, scope:this
  307. ,fn:function(key, e) {
  308. var sm = this.getSelectionModel();
  309. var node = sm.getSelectedNode();
  310. if(node && 0 !== node.getDepth() && node.isLeaf()) {
  311. this.openNode(node);
  312. }
  313. }},{
  314. // F2 = edit
  315. key:113, scope:this
  316. ,fn:function(key, e) {
  317. var sm = this.getSelectionModel();
  318. var node = sm.getSelectedNode();
  319. if(node && 0 !== node.getDepth() && this.enableRename && this.readOnly !== true) {
  320. this.treeEditor.triggerEdit(node);
  321. }
  322. }},{
  323. // Delete Key = Delete
  324. key:46, stopEvent:true, scope:this
  325. ,fn:function(key, e) {
  326. var sm = this.getSelectionModel();
  327. var node = sm.getSelectedNode();
  328. if(node && 0 !== node.getDepth() && this.enableDelete && this.readOnly !== true) {
  329. this.deleteNode(node);
  330. }
  331. }},{
  332. // Ctrl + E = reload
  333. key:69, ctrl:true, stopEvent:true, scope:this
  334. ,fn:function(key, e) {
  335. var sm = this.getSelectionModel();
  336. var node = sm.getSelectedNode();
  337. if(node) {
  338. node = node.isLeaf() ? node.parentNode : node;
  339. sm.select(node);
  340. node.reload();
  341. }
  342. }},{
  343. // Ctrl + -> = expand deep
  344. key:39, ctrl:true, stopEvent:true, scope:this
  345. ,fn:function(key, e) {
  346. var sm = this.getSelectionModel();
  347. var node = sm.getSelectedNode();
  348. if(node && !node.isLeaf()) {
  349. sm.select(node);
  350. node.expand.defer(1, node, [true]);
  351. }
  352. }},{
  353. // Ctrl + <- = collapse deep
  354. key:37, ctrl:true, scope:this, stopEvent:true
  355. ,fn:function(key, e) {
  356. var sm = this.getSelectionModel();
  357. var node = sm.getSelectedNode();
  358. if(node && !node.isLeaf()) {
  359. sm.select(node);
  360. node.collapse.defer(1, node, [true]);
  361. }
  362. }},{
  363. // Ctrl + N = New Directory
  364. key:78, ctrl:true, scope:this, stopEvent:true
  365. ,fn:function(key, e) {
  366. var sm, node;
  367. sm = this.getSelectionModel();
  368. node = sm.getSelectedNode();
  369. if(node && this.enableNewDir && this.readOnly !== true) {
  370. node = node.isLeaf() ? node.parentNode : node;
  371. this.createNewDir(node);
  372. }
  373. }}]
  374. // }}}
  375.  
  376. }); // eo apply
  377. // }}}
  378. // {{{
  379. // create loader
  380. if(!this.loader) {
  381. this.loader = new Ext.tree.TreeLoader({
  382. url:this.url
  383. ,baseParams:{cmd:'get'}
  384. ,listeners:{
  385. beforeload:{scope:this, fn:function(loader, node) {
  386. loader.baseParams.path = this.getPath(node);
  387. }}
  388. }
  389. });
  390. }
  391. // }}}
  392. // {{{
  393. // install top menu if configured
  394. if(true === this.topMenu) {
  395. this.tbar = [{
  396. text:this.fileText
  397. ,disabled:true
  398. ,scope:this
  399. ,menu:this.getContextMenu()
  400. }];
  401. }
  402. // }}}
  403.  
  404. // call parent
  405. Ext.ux.FileTreePanel.superclass.initComponent.apply(this, arguments);
  406.  
  407. // {{{
  408. // install treeEditor event handlers
  409. if(this.treeEditor) {
  410. // do not enter edit mode on selected node click
  411. this.treeEditor.beforeNodeClick = function(node,e){return true;};
  412.  
  413. // treeEditor event handlers
  414. this.treeEditor.on({
  415. complete:{scope:this, fn:this.onEditComplete}
  416. ,beforecomplete:{scope:this, fn:this.onBeforeEditComplete}
  417. });
  418. }
  419. // }}}
  420. // {{{
  421. // install event handlers
  422. this.on({
  423. contextmenu:{scope:this, fn:this.onContextMenu, stopEvent:true}
  424. ,dblclick:{scope:this, fn:this.onDblClick}
  425. ,beforenodedrop:{scope:this, fn:this.onBeforeNodeDrop}
  426. ,nodedrop:{scope:this, fn:this.onNodeDrop}
  427. ,nodedragover:{scope:this, fn:this.onNodeDragOver}
  428. });
  429.  
  430. // }}}
  431. // {{{
  432. // add events
  433. this.addEvents(
  434. /**
  435. * @event beforeopen
  436. * Fires before file open. Return false to cancel the event
  437. * @param {Ext.ux.FileTreePanel} this
  438. * @param {String} fileName name of the file being opened
  439. * @param {String} url url of the file being opened
  440. * @param {String} mode open mode
  441. */
  442. 'beforeopen'
  443. /**
  444. * @event open
  445. * Fires after file open has been initiated
  446. * @param {Ext.ux.FileTreePanel} this
  447. * @param {String} fileName name of the file being opened
  448. * @param {String} url url of the file being opened
  449. * @param {String} mode open mode
  450. */
  451. ,'open'
  452. /**
  453. * @event beforerename
  454. * Fires after the user completes file name editing
  455. * but before the file is renamed. Return false to cancel the event
  456. * @param {Ext.ux.FileTreePanel} this
  457. * @param {Ext.tree.AsyncTreeNode} node being renamed
  458. * @param {String} newPath including file name
  459. * @param {String} oldPath including file name
  460. */
  461. ,'beforerename'
  462. /**
  463. * @event rename
  464. * Fires after the file has been successfully renamed
  465. * @param {Ext.ux.FileTreePanel} this
  466. * @param {Ext.tree.AsyncTreeNode} node that has been renamed
  467. * @param {String} newPath including file name
  468. * @param {String} oldPath including file name
  469. */
  470. ,'rename'
  471. /**
  472. * @event renamefailure
  473. * Fires after a failure when renaming file
  474. * @param {Ext.ux.FileTreePanel} this
  475. * @param {Ext.tree.AsyncTreeNode} node rename of which failed
  476. * @param {String} newPath including file name
  477. * @param {String} oldPath including file name
  478. */
  479. ,'renamefailure'
  480. /**
  481. * @event beforedelete
  482. * Fires before a file or directory is deleted. Return false to cancel the event.
  483. * @param {Ext.ux.FileTreePanel} this
  484. * @param {Ext.tree.AsyncTreeNode} node being deleted
  485. */
  486. ,'beforedelete'
  487. /**
  488. * @event delete
  489. * Fires after a file or directory has been deleted
  490. * @param {Ext.ux.FileTreePanel} this
  491. * @param {String} path including file name that has been deleted
  492. */
  493. ,'delete'
  494. /**
  495. * @event deletefailure
  496. * Fires if node delete failed
  497. * @param {Ext.ux.FileTreePanel} this
  498. * @param {Ext.tree.AsyncTreeNode} node delete of which failed
  499. */
  500. ,'deletefailure'
  501. /**
  502. * @event beforenewdir
  503. * Fires before new directory is created. Return false to cancel the event
  504. * @param {Ext.ux.FileTreePanel} this
  505. * @param {Ext.tree.AsyncTreeNode} node under which the new directory is being created
  506. */
  507. ,'beforenewdir'
  508. /**
  509. * @event newdir
  510. * Fires after the new directory has been successfully created
  511. * @param {Ext.ux.FileTreePanel} this
  512. * @param {Ext.tree.AsyncTreeNode} new node/directory that has been created
  513. */
  514. ,'newdir'
  515. /**
  516. * @event newdirfailure
  517. * Fires if creation of new directory failed
  518. * @param {Ext.ux.FileTreePanel} this
  519. * @param {String} path creation of which failed
  520. */
  521. ,'newdirfailure'
  522. ); // eo addEvents
  523. // }}}
  524.  
  525. } // eo function initComponent
  526. // }}}
  527. // {{{
  528. /**
  529. * onRender override - just expands root node if configured
  530. * @private
  531. */
  532. ,onRender:function() {
  533. // call parent
  534. Ext.ux.FileTreePanel.superclass.onRender.apply(this, arguments);
  535.  
  536. if(true === this.topMenu) {
  537. this.topMenu = Ext.getCmp(this.getTopToolbar().items.itemAt(0).id);
  538. this.getSelectionModel().on({
  539. scope:this
  540. ,selectionchange:function(sm, node) {
  541. var disable = node ? false : true;
  542. disable = disable || this.readOnly;
  543. this.topMenu.setDisabled(disable);
  544. }
  545. });
  546. Ext.apply(this.topMenu, {
  547. showMenu:function() {
  548. this.showContextMenu(false);
  549. }.createDelegate(this)
  550. // ,menu:this.getContextMenu()
  551. });
  552. }
  553.  
  554. // expand root node if so configured
  555. if(this.expandOnRender) {
  556. this.root.expand();
  557. }
  558.  
  559. // prevent default browser context menu to appear
  560. this.el.on({
  561. contextmenu:{fn:function(){return false;},stopEvent:true}
  562. });
  563.  
  564. // setup loading mask if configured
  565. if(true === this.loadMask) {
  566. this.loader.on({
  567. scope:this.el
  568. ,beforeload:this.el.mask.createDelegate(this.el, [this.loadingText + '...'])
  569. ,load:this.el.unmask
  570. ,loadexception:this.el.unmask
  571. });
  572. }
  573.  
  574. } // eo function onRender
  575. // }}}
  576.  
  577. // new methods
  578. // {{{
  579. /**
  580. * runs after an Ajax requested command has completed/failed
  581. * @private
  582. * @param {Object} options Options used for the request
  583. * @param {Boolean} success true if ajax call was successful (cmd may have failed)
  584. * @param {Object} response ajax call response object
  585. */
  586. ,cmdCallback:function(options, success, response) {
  587. var i, o, node;
  588. var showMsg = true;
  589.  
  590. // process Ajax success
  591. if(true === success) {
  592.  
  593. // try to decode JSON response
  594. try {
  595. o = Ext.decode(response.responseText);
  596. }
  597. catch(ex) {
  598. this.showError(response.responseText);
  599. }
  600.  
  601. // process command success
  602. if(true === o.success) {
  603. switch(options.params.cmd) {
  604. case 'delete':
  605. if(true !== this.eventsSuspended) {
  606. this.fireEvent('delete', this, this.getPath(options.node));
  607. }
  608. options.node.parentNode.removeChild(options.node);
  609. break;
  610.  
  611. case 'newdir':
  612. if(true !== this.eventsSuspended) {
  613. this.fireEvent('newdir', this, options.node);
  614. }
  615. break;
  616.  
  617. case 'rename':
  618. this.updateCls(options.node, options.params.oldname);
  619. if(true !== this.eventsSuspended) {
  620. this.fireEvent('rename', this, options.node, options.params.newname, options.params.oldname);
  621. }
  622. break;
  623. }
  624. } // eo process command success
  625. // process command failure
  626. else {
  627. switch(options.params.cmd) {
  628.  
  629. case 'rename':
  630. // handle drag & drop rename error
  631. if(options.oldParent) {
  632. options.oldParent.appendChild(options.node);
  633. }
  634. // handle simple rename error
  635. else {
  636. options.node.setText(options.oldName);
  637. }
  638. // signal failure to onNodeDrop
  639. if(options.e) {
  640. options.e.failure = true;
  641. }
  642. if(true !== this.eventsSuspended) {
  643. this.fireEvent('renamefailure', this, options.node, options.params.newname, options.params.oldname);
  644. }
  645. break;
  646.  
  647. case 'newdir':
  648. if(false !== this.eventsSuspended) {
  649. this.fireEvent('newdirfailure', this, options.params.dir);
  650. }
  651. options.node.parentNode.removeChild(options.node);
  652. break;
  653.  
  654. case 'delete':
  655. if(true !== this.eventsSuspended) {
  656. this.fireEvent('deletefailure', this, options.node);
  657. }
  658. options.node.parentNode.reload.defer(1, options.node.parentNode);
  659. break;
  660.  
  661. default:
  662. this.root.reload();
  663. break;
  664. }
  665.  
  666. // show default message box with server error
  667. this.showError(o.error || response.responseText);
  668. } // eo process command failure
  669. } // eo process Ajax success
  670.  
  671. // process Ajax failure
  672. else {
  673. this.showError(response.responseText);
  674. }
  675. } // eo function cmdCallback
  676. // }}}
  677. // {{{
  678. /**
  679. * displays overwrite confirm msg box and runs passed callback if response is yes
  680. * @private
  681. * @param {String} filename File to overwrite
  682. * @param {Function} callback Function to call on yes response
  683. * @param {Object} scope Scope for callback (defaults to this)
  684. */
  685. ,confirmOverwrite:function(filename, callback, scope) {
  686. Ext.Msg.show({
  687. title:this.confirmText
  688. ,msg:String.format(this.existsText, filename) + '. ' + this.overwriteText
  689. ,icon:Ext.Msg.QUESTION
  690. ,buttons:Ext.Msg.YESNO
  691. ,fn:callback.createDelegate(scope || this)
  692. });
  693. }
  694. // }}}
  695. // {{{
  696. /**
  697. * creates new directory (node)
  698. * @private
  699. * @param {Ext.tree.AsyncTreeNode} node
  700. */
  701. ,createNewDir:function(node) {
  702.  
  703. // fire beforenewdir event
  704. if(true !== this.eventsSuspended && false === this.fireEvent('beforenewdir', this, node)) {
  705. return;
  706. }
  707.  
  708. var treeEditor = this.treeEditor;
  709. var newNode;
  710.  
  711. // get node to append the new directory to
  712. var appendNode = node.isLeaf() ? node.parentNode : node;
  713.  
  714. // create new folder after the appendNode is expanded
  715. appendNode.expand(false, false, function(n) {
  716. // create new node
  717. newNode = n.appendChild(new Ext.tree.AsyncTreeNode({text:this.newdirText, iconCls:'folder'}));
  718.  
  719. // setup one-shot event handler for editing completed
  720. treeEditor.on({
  721. complete:{
  722. scope:this
  723. ,single:true
  724. ,fn:this.onNewDir
  725. }}
  726. );
  727.  
  728. // creating new directory flag
  729. treeEditor.creatingNewDir = true;
  730.  
  731. // start editing after short delay
  732. (function(){treeEditor.triggerEdit(newNode);}.defer(10));
  733. // expand callback needs to run in this context
  734. }.createDelegate(this));
  735.  
  736. } // eo function creatingNewDir
  737. // }}}
  738. // {{{
  739. /**
  740. * deletes the passed node
  741. * @private
  742. * @param {Ext.tree.AsyncTreeNode} node
  743. */
  744. ,deleteNode:function(node) {
  745. // fire beforedelete event
  746. if(true !== this.eventsSuspended && false === this.fireEvent('beforedelete', this, node)) {
  747. return;
  748. }
  749.  
  750. Ext.Msg.show({
  751. title:this.deleteText
  752. ,msg:this.reallyWantText + ' ' + this.deleteText.toLowerCase() + ' <b>' + node.text + '</b>?'
  753. ,icon:Ext.Msg.WARNING
  754. ,buttons:Ext.Msg.YESNO
  755. ,scope:this
  756. ,fn:function(response) {
  757. // do nothing if answer is not yes
  758. if('yes' !== response) {
  759. this.getEl().dom.focus();
  760. return;
  761. }
  762. // setup request options
  763. var options = {
  764. url:this.deleteUrl || this.url
  765. ,method:this.method
  766. ,scope:this
  767. ,callback:this.cmdCallback
  768. ,node:node
  769. ,params:{
  770. cmd:'delete'
  771. ,file:this.getPath(node)
  772. }
  773. };
  774. Ext.Ajax.request(options);
  775. }
  776. });
  777. } // eo function deleteNode
  778. // }}}
  779. // {{{
  780. /**
  781. * requests file download from server
  782. * @private
  783. * @param {String} path Full path including file name but relative to server root path
  784. */
  785. ,downloadFile:function(path) {
  786.  
  787. // create hidden target iframe
  788. var id = Ext.id();
  789. var frame = document.createElement('iframe');
  790. frame.id = id;
  791. frame.name = id;
  792. frame.className = 'x-hidden';
  793. if(Ext.isIE) {
  794. frame.src = Ext.SSL_SECURE_URL;
  795. }
  796.  
  797. document.body.appendChild(frame);
  798.  
  799. if(Ext.isIE) {
  800. document.frames[id].name = id;
  801. }
  802.  
  803. var form = Ext.DomHelper.append(document.body, {
  804. tag:'form'
  805. ,method:'post'
  806. ,action:this.downloadUrl || this.url
  807. ,target:id
  808. });
  809.  
  810. document.body.appendChild(form);
  811.  
  812. var hidden;
  813.  
  814. // append cmd to form
  815. hidden = document.createElement('input');
  816. hidden.type = 'hidden';
  817. hidden.name = 'cmd';
  818. hidden.value = 'download';
  819. form.appendChild(hidden);
  820.  
  821. // append path to form
  822. hidden = document.createElement('input');
  823. hidden.type = 'hidden';
  824. hidden.name = 'path';
  825. hidden.value = path;
  826. form.appendChild(hidden);
  827.  
  828. var callback = function() {
  829. Ext.EventManager.removeListener(frame, 'load', callback, this);
  830. setTimeout(function() {document.body.removeChild(form);}, 100);
  831. setTimeout(function() {document.body.removeChild(frame);}, 110);
  832. };
  833.  
  834. Ext.EventManager.on(frame, 'load', callback, this);
  835.  
  836. form.submit();
  837. }
  838. // }}}
  839. // {{{
  840. /**
  841. * returns (and lazy create) the context menu
  842. * @private
  843. */
  844. ,getContextMenu:function() {
  845. // lazy create context menu
  846. if(!this.contextmenu) {
  847. var config = {
  848. singleUpload:this.singleUpload
  849. ,maxFileSize:this.maxFileSize
  850. ,enableProgress:this.enableProgress
  851. };
  852. if(this.baseParams) {
  853. config.baseParams = this.baseParams;
  854. }
  855. this.contextmenu = new Ext.ux.FileTreeMenu(config);
  856. this.contextmenu.on({click:{scope:this, fn:this.onContextClick}});
  857.  
  858. this.uploadPanel = this.contextmenu.getItemByCmd('upload-panel').component;
  859. this.uploadPanel.on({
  860. beforeupload:{scope:this, fn:this.onBeforeUpload}
  861. ,allfinished:{scope:this, fn:this.onAllFinished}
  862. });
  863. this.uploadPanel.setUrl(this.uploadUrl || this.url);
  864. }
  865. return this.contextmenu;
  866. } // eo function getContextMenu
  867. // }}}
  868. // {{{
  869. /**
  870. * returns file class based on name extension
  871. * @private
  872. * @param {String} name File name to get class of
  873. */
  874. ,getFileCls:function(name) {
  875. var atmp = name.split('.');
  876. if(1 === atmp.length) {
  877. return this.fileCls;
  878. }
  879. else {
  880. return this.fileCls + '-' + atmp.pop().toLowerCase();
  881. }
  882. }
  883. // }}}
  884. // {{{
  885. /**
  886. * returns path of node (file/directory)
  887. * @private
  888. */
  889. ,getPath:function(node) {
  890. var path, p, a;
  891.  
  892. // get path for non-root node
  893. if(node !== this.root) {
  894. p = node.parentNode;
  895. a = [node.text];
  896. while(p && p !== this.root) {
  897. a.unshift(p.text);
  898. p = p.parentNode;
  899. }
  900. a.unshift(this.root.attributes.path || '');
  901. path = a.join(this.pathSeparator);
  902. }
  903.  
  904. // path for root node is it's path attribute
  905. else {
  906. path = node.attributes.path || '';
  907. }
  908.  
  909. // a little bit of security: strip leading / or .
  910. // full path security checking has to be implemented on server
  911. path = path.replace(/^[\/\.]*/, '');
  912. return path;
  913. } // eo function getPath
  914. // }}}
  915. // {{{
  916. /**
  917. * returns true if node has child with the specified name (text)
  918. * @private
  919. * @param {Ext.data.Node} node
  920. * @param {String} childName
  921. */
  922. ,hasChild:function(node, childName) {
  923. return (node.isLeaf() ? node.parentNode : node).findChild('text', childName) !== null;
  924. }
  925. // }}}
  926. // {{{
  927. /**
  928. * Hides context menu
  929. * @return {Ext.ux.FileTreeMenu} this
  930. */
  931. ,hideContextMenu:function() {
  932. if(this.contextmenu && this.contextmenu.isVisible()) {
  933. this.contextmenu.hide();
  934. }
  935. return this;
  936. } // eo function hideContextMenu
  937. // }}}
  938. // {{{
  939. /**
  940. * called before editing is completed - allows edit cancellation
  941. * @private
  942. * @param {TreeEditor} editor
  943. * @param {String} newName
  944. * @param {String} oldName
  945. */
  946. ,onBeforeEditComplete:function(editor, newName, oldName) {
  947. if(editor.cancellingEdit) {
  948. editor.cancellingEdit = false;
  949. return;
  950. }
  951. var oldPath = this.getPath(editor.editNode);
  952. var newPath = oldPath.replace(/\/[^\\]+$/, '/' + newName);
  953.  
  954. if(false === this.fireEvent('beforerename', this, editor.editNode, newPath, oldPath)) {
  955. editor.cancellingEdit = true;
  956. editor.cancelEdit();
  957. return false;
  958. }
  959. }
  960. // }}}
  961. // {{{
  962. /**
  963. * runs before node is dropped
  964. * @private
  965. * @param {Object} e dropEvent object
  966. */
  967. ,onBeforeNodeDrop:function(e) {
  968.  
  969. // source node, node being dragged
  970. var s = e.dropNode;
  971.  
  972. // destination node (dropping on this node)
  973. var d = e.target.leaf ? e.target.parentNode : e.target;
  974.  
  975. // node has been dropped within the same parent
  976. if(s.parentNode === d) {
  977. return false;
  978. }
  979.  
  980. // check if same name exists in the destination
  981. // this works only if destination node is loaded
  982. if(this.hasChild(d, s.text) && undefined === e.confirmed) {
  983. this.confirmOverwrite(s.text, function(response) {
  984. e.confirmed = 'yes' === response;
  985. this.onBeforeNodeDrop(e);
  986. });
  987. return false;
  988. }
  989. if(false === e.confirmed) {
  990. return false;
  991. }
  992.  
  993. e.confirmed = undefined;
  994. e.oldParent = s.parentNode;
  995.  
  996. var oldName = this.getPath(s);
  997. var newName = this.getPath(d) + '/' + s.text;
  998.  
  999. // fire beforerename event
  1000. if(true !== this.eventsSuspended && false === this.fireEvent('beforerename', this, s, newName, oldName)) {
  1001. return false;
  1002. }
  1003.  
  1004. var options = {
  1005. url:this.renameUrl || this.url
  1006. ,method:this.method
  1007. ,scope:this
  1008. ,callback:this.cmdCallback
  1009. ,node:s
  1010. ,oldParent:s.parentNode
  1011. ,e:e
  1012. ,params:{
  1013. cmd:'rename'
  1014. ,oldname:oldName
  1015. ,newname:newName
  1016. }
  1017. };
  1018. Ext.Ajax.request(options);
  1019. return true;
  1020. }
  1021. // }}}
  1022. // {{{
  1023. /**
  1024. * sets uploadPanel's destination path
  1025. * @private
  1026. */
  1027. ,onBeforeUpload:function(uploadPanel) {
  1028.  
  1029. var menu = this.getContextMenu();
  1030. var path = this.getPath(menu.node);
  1031. if(menu.node.isLeaf()) {
  1032. path = path.replace(/\/[^\/]+$/, '', path);
  1033. }
  1034. uploadPanel.setPath(path);
  1035.  
  1036. } // eo function onBeforeUpload
  1037. // }}}
  1038. // {{{
  1039. /**
  1040. * reloads tree node on upload finish
  1041. * @private
  1042. */
  1043. ,onAllFinished:function(uploader) {
  1044. var menu = this.getContextMenu();
  1045. (menu.node.isLeaf() ? menu.node.parentNode : menu.node).reload();
  1046. } // eo function onAllFinished
  1047. // }}}
  1048. // {{{
  1049. /**
  1050. * @private
  1051. * context menu click handler
  1052. * @param {Ext.menu.Menu} context menu
  1053. * @param {Ext.menu.Item} item clicked
  1054. * @param {Ext.EventObject} raw event
  1055. */
  1056. ,onContextClick:function(menu, item, e) {
  1057. if(item.disabled) {
  1058. return;
  1059. }
  1060. var node = menu.node;
  1061. if(!node) {
  1062. node = menu.parentMenu.node;
  1063. }
  1064. switch(item.cmd) {
  1065. case 'reload':
  1066. node.reload();
  1067. break;
  1068.  
  1069. case 'expand':
  1070. node.expand(true);
  1071. break;
  1072.  
  1073. case 'collapse':
  1074. node.collapse(true);
  1075. break;
  1076.  
  1077. case 'open':
  1078. this.openNode(node);
  1079. break;
  1080.  
  1081. case 'open-self':
  1082. this.openNode(node, '_self');
  1083. break;
  1084.  
  1085. case 'open-popup':
  1086. this.openNode(node, 'popup');
  1087. break;
  1088.  
  1089. case 'open-blank':
  1090. this.openNode(node, '_blank');
  1091. break;
  1092.  
  1093. case 'open-dwnld':
  1094. this.openNode(node, 'download');
  1095. break;
  1096.  
  1097. case 'rename':
  1098. this.treeEditor.triggerEdit(node);
  1099. break;
  1100.  
  1101. case 'delete':
  1102. this.deleteNode(node);
  1103. break;
  1104.  
  1105. case 'newdir':
  1106. this.createNewDir(node);
  1107. break;
  1108.  
  1109. default:
  1110. break;
  1111. }
  1112. } // eo function onContextClick
  1113. // }}}
  1114. // {{{
  1115. /**
  1116. * contextmenu event handler
  1117. * @private
  1118. */
  1119. ,onContextMenu:function(node, e) {
  1120. if(this.readOnly) {
  1121. return false;
  1122. }
  1123. this.showContextMenu(node);
  1124.  
  1125. return false;
  1126. } // eo function onContextMenu
  1127. // }}}
  1128. // {{{
  1129. /**
  1130. * dblclick handlers
  1131. * @private
  1132. */
  1133. ,onDblClick:function(node, e) {
  1134. this.openNode(node);
  1135. } // eo function onDblClick
  1136. // }}}
  1137. // {{{
  1138. /**
  1139. * Destroys the FileTreePanel and sub-components
  1140. * @private
  1141. */
  1142. ,onDestroy:function() {
  1143.  
  1144. // destroy contextmenu
  1145. if(this.contextmenu) {
  1146. this.contextmenu.purgeListeners();
  1147. this.contextmenu.destroy();
  1148. this.contextmenu = null;
  1149. }
  1150.  
  1151. // destroy treeEditor
  1152. if(this.treeEditor) {
  1153. this.treeEditor.purgeListeners();
  1154. this.treeEditor.destroy();
  1155. this.treeEditor = null;
  1156. }
  1157.  
  1158. // remover reference to treeSorter
  1159. if(this.treeSorter) {
  1160. this.treeSorter = null;
  1161. }
  1162.  
  1163. // call parent
  1164. Ext.ux.FileTreePanel.superclass.onDestroy.call(this);
  1165.  
  1166. } // eo function onDestroy
  1167. // }}}
  1168. // {{{
  1169. /**
  1170. * runs when editing of a node (rename) is completed
  1171. * @private
  1172. * @param {Ext.Editor} editor
  1173. * @param {String} newName
  1174. * @param {String} oldName
  1175. */
  1176. ,onEditComplete:function(editor, newName, oldName) {
  1177.  
  1178. var node = editor.editNode;
  1179.  
  1180. if(newName === oldName || editor.creatingNewDir) {
  1181. editor.creatingNewDir = false;
  1182. return;
  1183. }
  1184. var path = this.getPath(node.parentNode);
  1185. var options = {
  1186. url:this.renameUrl || this.url
  1187. ,method:this.method
  1188. ,scope:this
  1189. ,callback:this.cmdCallback
  1190. ,node:node
  1191. ,oldName:oldName
  1192. ,params:{
  1193. cmd:'rename'
  1194. ,oldname:path + '/' + oldName
  1195. ,newname:path + '/' + newName
  1196. }
  1197. };
  1198. Ext.Ajax.request(options);
  1199. }
  1200. // }}}
  1201. // {{{
  1202. /**
  1203. * create new directory handler
  1204. * @private
  1205. * runs after editing of new directory name is completed
  1206. * @param {Ext.Editor} editor
  1207. */
  1208. ,onNewDir:function(editor) {
  1209. var path = this.getPath(editor.editNode);
  1210. var options = {
  1211. url:this.newdirUrl || this.url
  1212. ,method:this.method
  1213. ,scope:this
  1214. ,node:editor.editNode
  1215. ,callback:this.cmdCallback
  1216. ,params:{
  1217. cmd:'newdir'
  1218. ,dir:path
  1219. }
  1220. };
  1221. Ext.Ajax.request(options);
  1222. }
  1223. // }}}
  1224. // {{{
  1225. /**
  1226. * called while dragging over, decides if drop is allowed
  1227. * @private
  1228. * @param {Object} dd event
  1229. */
  1230. ,onNodeDragOver:function(e) {
  1231. e.cancel = e.target.disabled || e.dropNode.parentNode === e.target.parentNode && e.target.isLeaf();
  1232. } // eo function onNodeDragOver
  1233. // }}}
  1234. // {{{
  1235. /**
  1236. * called when node is dropped
  1237. * @private
  1238. * @param {Object} dd event
  1239. */
  1240. ,onNodeDrop:function(e) {
  1241.  
  1242. // failure can be signalled by cmdCallback
  1243. // put drop node to the original parent in that case
  1244. if(true === e.failure) {
  1245. e.oldParent.appendChild(e.dropNode);
  1246. return;
  1247. }
  1248.  
  1249. // if we already have node with the same text, remove the duplicate
  1250. var sameNode = e.dropNode.parentNode.findChild('text', e.dropNode.text);
  1251. if(sameNode && sameNode !== e.dropNode) {
  1252. sameNode.parentNode.removeChild(sameNode);
  1253. }
  1254. }
  1255. // }}}
  1256. // {{{
  1257. /**
  1258. * Opens node
  1259. * @param {Ext.tree.AsyncTreeNode} node
  1260. * @param {String} mode Can be "_self", "_blank", or "popup". Defaults to (this.openMode)
  1261. */
  1262. ,openNode:function(node, mode) {
  1263.  
  1264. if(!this.enableOpen) {
  1265. return;
  1266. }
  1267.  
  1268. mode = mode || this.openMode;
  1269.  
  1270. var url;
  1271. var path;
  1272. if(node.isLeaf()) {
  1273. path = this.getPath(node);
  1274. url = this.hrefPrefix + path + this.hrefSuffix;
  1275.  
  1276. // fire beforeopen event
  1277. if(true !== this.eventsSuspended && false === this.fireEvent('beforeopen', this, node.text, url, mode)) {
  1278. return;
  1279. }
  1280.  
  1281. switch(mode) {
  1282. case 'popup':
  1283. if(!this.popup || this.popup.closed) {
  1284. this.popup = window.open(url, this.hrefTarget, this.popupFeatures);
  1285. }
  1286. this.popup.location = url;
  1287. if(this.focusPopup) {
  1288. this.popup.focus();
  1289. }
  1290. break;
  1291.  
  1292. case '_self':
  1293. window.location = url;
  1294. break;
  1295.  
  1296. case '_blank':
  1297. window.open(url);
  1298. break;
  1299.  
  1300. case 'download':
  1301. this.downloadFile(path);
  1302. break;
  1303. }
  1304.  
  1305. // fire open event
  1306. if(true !== this.eventsSuspended) {
  1307. this.fireEvent('open', this, node.text, url, mode);
  1308. }
  1309. }
  1310.  
  1311. }
  1312. // }}}
  1313. // {{{
  1314. /**
  1315. * Sets/Unsets delete of files/directories disabled/enabled
  1316. * @param {Boolean} disabled
  1317. * @return {Ext.ux.FileTreePanel} this
  1318. */
  1319. ,setDeleteDisabled:function(disabled) {
  1320. disabled = !(!disabled);
  1321. if(!this.enableDelete === disabled) {
  1322. return this;
  1323. }
  1324. this.hideContextMenu();
  1325. this.enableDelete = !disabled;
  1326. } // eo function setDeleteDisabled
  1327. // }}}
  1328. // {{{
  1329. /**
  1330. * Sets/Unsets creation of new directory disabled/enabled
  1331. * @param {Boolean} disabled
  1332. * @return {Ext.ux.FileTreePanel} this
  1333. */
  1334. ,setNewdirDisabled:function(disabled) {
  1335. disabled = !(!disabled);
  1336. if(!this.enableNewDir === disabled) {
  1337. return this;
  1338. }
  1339. this.hideContextMenu();
  1340. this.enableNewDir = !disabled;
  1341.  
  1342. } // eo function setNewdirDisabled
  1343. // }}}
  1344. // {{{
  1345. /**
  1346. * Sets/Unsets open files disabled/enabled
  1347. * @param {Boolean} disabled
  1348. * @return {Ext.ux.FileTreePanel} this
  1349. */
  1350. ,setOpenDisabled:function(disabled) {
  1351. disabled = !(!disabled);
  1352. if(!this.enableOpen === disabled) {
  1353. return this;
  1354. }
  1355. this.hideContextMenu();
  1356. this.enableOpen = !disabled;
  1357.  
  1358. return this;
  1359. } // eo function setOpenDisabled
  1360. // }}}
  1361. // {{{
  1362. /**
  1363. * Sets/Unsets this tree to/from readOnly state
  1364. * @param {Boolean} readOnly
  1365. * @return {Ext.ux.FileTreePanel} this
  1366. */
  1367. ,setReadOnly:function(readOnly) {
  1368. readOnly = !(!readOnly);
  1369. if(this.readOnly === readOnly) {
  1370. return this;
  1371. }
  1372. this.hideContextMenu();
  1373. if(this.dragZone) {
  1374. this.dragZone.locked = readOnly;
  1375. }
  1376. this.readOnly = readOnly;
  1377.  
  1378. return this;
  1379.  
  1380. } // eo function setReadOnly
  1381. // }}}
  1382. // {{{
  1383. /**
  1384. * Sets/Unsets rename of files/directories disabled/enabled
  1385. * @param {Boolean} disabled
  1386. * @return {Ext.ux.FileTreePanel} this
  1387. */
  1388. ,setRenameDisabled:function(disabled) {
  1389. disabled = !(!disabled);
  1390. if(!this.enableRename === disabled) {
  1391. return this;
  1392. }
  1393. this.hideContextMenu();
  1394. if(this.dragZone) {
  1395. this.dragZone.locked = disabled;
  1396. }
  1397. this.enableRename = !disabled;
  1398.  
  1399. return this;
  1400. } // eo function setRenameDisabled
  1401. // }}}
  1402. // {{{
  1403. /**
  1404. * Sets/Unsets uploading of files disabled/enabled
  1405. * @param {Boolean} disabled
  1406. * @return {Ext.ux.FileTreePanel} this
  1407. */
  1408. ,setUploadDisabled:function(disabled) {
  1409. disabled = !(!disabled);
  1410. if(!this.enableUpload === disabled) {
  1411. return this;
  1412. }
  1413. this.hideContextMenu();
  1414. this.enableUpload = !disabled;
  1415.  
  1416. return this;
  1417. } // of function setUploadDisabled
  1418. // }}}
  1419. // {{{
  1420. /**
  1421. * adjusts context menu depending on many things and shows it
  1422. * @private
  1423. * @param {Ext.tree.AsyncTreeNode} node Node on which was right-clicked
  1424. */
  1425. ,showContextMenu:function(node) {
  1426.  
  1427. // setup node alignment
  1428. var topAlign = false;
  1429. var alignEl = this.topMenu ? this.topMenu.getEl() : this.body;
  1430.  
  1431. if(!node) {
  1432. node = this.getSelectionModel().getSelectedNode();
  1433. topAlign = true;
  1434. }
  1435. else {
  1436. alignEl = node.getUI().getEl();
  1437. }
  1438. if(!node) {
  1439. return;
  1440. }
  1441.  
  1442. var menu = this.getContextMenu();
  1443. menu.node = node;
  1444.  
  1445. // set node name
  1446. menu.getItemByCmd('nodename').setText(Ext.util.Format.ellipsis(node.text, 22));
  1447.  
  1448. // enable/disable items depending on node clicked
  1449. menu.setItemDisabled('open', !node.isLeaf());
  1450. menu.setItemDisabled('reload', node.isLeaf());
  1451. menu.setItemDisabled('expand', node.isLeaf());
  1452. menu.setItemDisabled('collapse', node.isLeaf());
  1453. menu.setItemDisabled('delete', node === this.root || node.disabled);
  1454. menu.setItemDisabled('rename', this.readOnly || node === this.root || node.disabled);
  1455. menu.setItemDisabled('newdir', this.readOnly || (node.isLeaf() ? node.parentNode.disabled : node.disabled));
  1456. menu.setItemDisabled('upload', node.isLeaf() ? node.parentNode.disabled : node.disabled);
  1457. menu.setItemDisabled('upload-panel', node.isLeaf() ? node.parentNode.disabled : node.disabled);
  1458.  
  1459. // show/hide logic
  1460. menu.getItemByCmd('open').setVisible(this.enableOpen);
  1461. menu.getItemByCmd('delete').setVisible(this.enableDelete);
  1462. menu.getItemByCmd('newdir').setVisible(this.enableNewDir);
  1463. menu.getItemByCmd('rename').setVisible(this.enableRename);
  1464. menu.getItemByCmd('upload').setVisible(this.enableUpload);
  1465. menu.getItemByCmd('upload-panel').setVisible(this.enableUpload);
  1466. menu.getItemByCmd('sep-upload').setVisible(this.enableUpload);
  1467. menu.getItemByCmd('sep-collapse').setVisible(this.enableNewDir || this.enableDelete || this.enableRename);
  1468.  
  1469. // select node
  1470. node.select();
  1471.  
  1472. // show menu
  1473. if(topAlign) {
  1474. menu.showAt(menu.getEl().getAlignToXY(alignEl, 'tl-bl?'));
  1475. }
  1476. else {
  1477. menu.showAt(menu.getEl().getAlignToXY(alignEl, 'tl-tl?', [0, 18]));
  1478. }
  1479. } // eo function
  1480. // }}}
  1481. // {{{
  1482. /**
  1483. * universal show error function
  1484. * @private
  1485. * @param {String} msg message
  1486. * @param {String} title title
  1487. */
  1488. ,showError:function(msg, title) {
  1489. Ext.Msg.show({
  1490. title:title || this.errorText
  1491. ,msg:Ext.util.Format.ellipsis(msg, this.maxMsgLen)
  1492. ,fixCursor:true
  1493. ,icon:Ext.Msg.ERROR
  1494. ,buttons:Ext.Msg.OK
  1495. ,minWidth:1200 > String(msg).length ? 360 : 600
  1496. });
  1497. } // eo function showError
  1498. // }}}
  1499. // {{{
  1500. /**
  1501. * updates class of leaf after rename
  1502. * @private
  1503. * @param {Ext.tree.AsyncTreeNode} node Node to update class of
  1504. * @param {String} oldName Name the node had before
  1505. */
  1506. ,updateCls:function(node, oldName) {
  1507. if(node.isLeaf()) {
  1508. Ext.fly(node.getUI().iconNode).removeClass(this.getFileCls(oldName));
  1509. Ext.fly(node.getUI().iconNode).addClass(this.getFileCls(node.text));
  1510. }
  1511. }
  1512. // }}}
  1513.  
  1514. }); // eo extend
  1515.  
  1516. // register xtype
  1517. Ext.reg('filetreepanel', Ext.ux.FileTreePanel);
  1518.  
  1519. // eof