Source of
js/Ext.ux.FileTreePanel.js:
// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
* Ext.ux.FileTreePanel
*
* @author Ing. Jozef Sakáloš
* @version $Id: Ext.ux.FileTreePanel.js 266 2008-05-18 23:24:47Z jozo $
* @date 13. March 2008
*
* @license Ext.ux.FileTreePanel is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
/*global Ext, window, document, setTimeout */
/**
* @class Ext.ux.FileTreePanel
* @extends Ext.tree.TreePanel
*/
Ext
.
ux
.
FileTreePanel
=
Ext
.
extend
(
Ext
.
tree
.
TreePanel
, {
// config variables overridable from outside
// {{{
/**
* @cfg {Object} baseParams This object is not used directly by FileTreePanel but it is
* propagated to lower level objects instead. Included here for convenience.
*/
/**
* @cfg {String} confirmText Text to display as title of confirmation message box
*/
confirmText
:
'Confirm'
/**
* @cfg {Boolean} containerScroll true to register
* this container with ScrollManager (defaults to true)
*/
,
containerScroll
:
true
/**
* @cfg {String} deleteText Delete text (for message box title or other displayed texts)
*/
,
deleteText
:
'Delete'
/**
* @cfg {String} deleteUrl URL to use when deleting; this.url is used if not set (defaults to undefined)
*/
/**
* @cfg {String} downloadUrl URL to use when downloading; this.url is used if not set (defaults to undefined)
*/
/**
* @cfg {Boolean} enableDD true to enable drag & drop of files and folders (defaults to true)
*/
,
enableDD
:
true
/**
* @cfg {Boolean) enableDelete true to enable to delete files and directories.
* If false context menu item is not shown (defaults to true)
*/
,
enableDelete
:
true
/**
* @cfg {Boolean) enableNewDir true to enable to create new directory.
* If false context menu item is not shown (defaults to true)
*/
,
enableNewDir
:
true
/**
* @cfg {Boolean) enableOpen true to enable open submenu
* If false context menu item is not shown (defaults to true)
*/
,
enableOpen
:
true
/**
* @cfg {Boolean} enableProgress true to enable querying server for progress information
* Passed to underlying uploader. Included here for convenience.
*/
,
enableProgress
:
true
/**
* @cfg {Boolean) enableRename true to enable to rename files and directories.
* If false context menu item is not shown (defaults to true)
*/
,
enableRename
:
true
/**
* @cfg {Boolean} enableSort true to enable sorting of tree. See also folderSort (defaults to true)
*/
,
enableSort
:
true
/**
* @cfg {Boolean) enableUpload true to enable to upload files.
* If false context menu item is not shown (defaults to true)
*/
,
enableUpload
:
true
/**
* @cfg {String} errorText Text to display for an error
*/
,
errorText
:
'Error'
/**
* @cfg {String} existsText Text to display in message box if file exists
*/
,
existsText
:
'File <b>{0}</b> already exists'
/**
* @cfg {Boolean} true to expand root node on FileTreePanel render (defaults to true)
*/
,
expandOnRender
:
true
/**
* @cfg {String} fileCls class prefix to add to nodes. "-extension" is appended to
* this prefix to form filetype class, for example: file-odt, file-pdf. These classes
* are used to display correct filetype icons in the tree. css file and icons must
* exist of course.
*/
,
fileCls
:
'file'
/**
* @cfg {String} fileText
*/
,
fileText
:
'File'
/**
* @cfg {Boolean} focusPopup true to focus new browser popup window for 'popup' openMode
* (defaults to true)
*/
,
focusPopup
:
true
/**
* @cfg {Boolean} folderSort true to place directories at the top of the tree (defaults to true)
*/
,
folderSort
:
true
/**
* @cfg {String} hrefPrefix Text to prepend before file href for file open command.
* (defaults to '')
*/
,
hrefPrefix
:
''
/**
* @cfg {String} hrefSuffix Text to append to file href for file open command.
* (defaults to '')
*/
,
hrefSuffix
:
''
/**
* @cfg {String} layout Layout to use for this panel (defaults to 'fit')
*/
,
layout
:
'fit'
/**
* @cfg {String} loadingText Text to use for load mask msg
*/
,
loadingText
:
'Loading'
/**
* @cfg {Boolean} loadMask True to mask tree panel while loading
*/
,
loadMask
:
false
/**
* @cfg {Number} maxFileSize Maximum upload file size in bytes
* This config property is propagated down to uploader for convenience
*/
,
maxFileSize
:
524288
/**
* @cfg {Number} maxMsgLen Maximum message length for message box (defaults to 2000).
* If message is longer Ext.util.Format.ellipsis is used to truncate it and append ...
*/
,
maxMsgLen
:
2000
/**
* @cfg {String} method Method to use when posting to server. Other valid value is 'get'
* (defaults to 'post')
*/
,
method
:
'post'
/**
* @cfg {String} newdirText Default name for new directories (defaults to 'New Folder')
*/
,
newdirText
:
'New Folder'
/**
* @cfg {String} newdirUrl URL to use when creating new directory;
* this.url is used if not set (defaults to undefined)
*/
/**
* @cfg {String} openMode Default file open mode. This mode is used when user dblclicks
* a file. Other valid values are '_self', '_blank' and 'download' (defaults to 'popup')
*/
,
openMode
:
'popup'
/**
* @cfg {String} overwriteText Text to use in overwrite confirmation message box
*/
,
overwriteText
:
'Do you want to overwrite it?'
/**
* @cfg {String} popupFeatures Features for new browser window opened by popup open mode
*/
,
popupFeatures
:
'width=800,height=600,dependent=1,scrollbars=1,resizable=1,toolbar=1'
/**
* @cfg {Boolean} readOnly true to disable write operations. treeEditor and context menu
* are not created if true (defaults to false)
*/
,
readOnly
:
false
/**
* @cfg {String} reallyWantText Text to display for that question
*/
,
reallyWantText
:
'Do you really want to'
/**
* @cfg {String} renameUrl URL to use when renaming; this.url is used if not set (defaults to undefined)
*/
/**
* @cfg {String} rootPath Relative path pointing to the directory that is root of this tree (defaults to 'root')
*/
,
rootPath
:
'root'
/**
* @cfg {String} rootText Text to display for root node (defaults to 'Tree Root')
*/
,
rootText
:
'Tree Root'
/**
* @cfg {Boolean} rootVisible true = root node visible, false = hidden (defaults to true)
*/
,
rootVisible
:
true
/**
* @cfg {Boolean} selectOnEdit true to select the edited text on edit start (defaults to true)
*/
,
selectOnEdit
:
true
/**
* @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
* This config property is propagated down to uploader for convenience
*/
,
singleUpload
:
false
/**
* @cfg {Boolean} topMenu true to create top toolbar with menu in addition to contextmenu
*/
,
topMenu
:
false
/**
* @cfg {String} url URL to use when communicating with server
*/
,
url
:
'filetree.php'
// }}}
// overrides
// {{{
/**
* called by Ext when instantiating
* @private
* @param {Object} config Configuration object
*/
,
initComponent
:
function
() {
// {{{
Ext
.
apply
(
this
, {
// create root node
root
:
new
Ext
.
tree
.
AsyncTreeNode
({
text
:
this
.
rootText
,
path
:
this
.
rootPath
,
rootVisible
:
this
.
rootVisible
,
allowDrag
:
false
})
// create treeEditor
,
treeEditor
:!
this
.
readOnly ?
new
Ext
.
tree
.
TreeEditor
(
this
, {
allowBlank
:
false
,
cancelOnEsc
:
true
,
completeOnEnter
:
true
,
ignoreNoChange
:
true
,
selectOnFocus
:
this
.
selectOnEdit
}) :
undefined
// drop config
,
dropConfig
:
this
.
dropConfig ?
this
.
dropConfig
: {
ddGroup
:
this
.
ddGroup
||
'TreeDD'
,
appendOnly
:
this
.
enableSort
,
expandDelay
:
3600000
// do not expand on drag over node
}
// create treeSorter
,
treeSorter
:
this
.
enableSort ?
new
Ext
.
tree
.
TreeSorter
(
this
, {
folderSort
:
this
.
folderSort
}) :
undefined
// {{{
,
keys
:[{
// Enter = open
key
:
Ext
.
EventObject
.
ENTER
,
scope
:
this
,
fn
:
function
(
key
,
e
) {
var
sm
=
this
.
getSelectionModel
();
var
node
=
sm
.
getSelectedNode
();
if
(
node
&&
0
!==
node
.
getDepth
() &&
node
.
isLeaf
()) {
this
.
openNode
(
node
);
}
}},{
// F2 = edit
key
:
113
,
scope
:
this
,
fn
:
function
(
key
,
e
) {
var
sm
=
this
.
getSelectionModel
();
var
node
=
sm
.
getSelectedNode
();
if
(
node
&&
0
!==
node
.
getDepth
() &&
this
.
enableRename
&&
this
.
readOnly
!==
true
) {
this
.
treeEditor
.
triggerEdit
(
node
);
}
}},{
// Delete Key = Delete
key
:
46
,
stopEvent
:
true
,
scope
:
this
,
fn
:
function
(
key
,
e
) {
var
sm
=
this
.
getSelectionModel
();
var
node
=
sm
.
getSelectedNode
();
if
(
node
&&
0
!==
node
.
getDepth
() &&
this
.
enableDelete
&&
this
.
readOnly
!==
true
) {
this
.
deleteNode
(
node
);
}
}},{
// Ctrl + E = reload
key
:
69
,
ctrl
:
true
,
stopEvent
:
true
,
scope
:
this
,
fn
:
function
(
key
,
e
) {
var
sm
=
this
.
getSelectionModel
();
var
node
=
sm
.
getSelectedNode
();
if
(
node
) {
node
=
node
.
isLeaf
()
? node
.
parentNode
:
node
;
sm
.
select
(
node
);
node
.
reload
();
}
}},{
// Ctrl + -> = expand deep
key
:
39
,
ctrl
:
true
,
stopEvent
:
true
,
scope
:
this
,
fn
:
function
(
key
,
e
) {
var
sm
=
this
.
getSelectionModel
();
var
node
=
sm
.
getSelectedNode
();
if
(
node
&& !
node
.
isLeaf
()) {
sm
.
select
(
node
);
node
.
expand
.
defer
(
1
,
node
, [
true
]);
}
}},{
// Ctrl + <- = collapse deep
key
:
37
,
ctrl
:
true
,
scope
:
this
,
stopEvent
:
true
,
fn
:
function
(
key
,
e
) {
var
sm
=
this
.
getSelectionModel
();
var
node
=
sm
.
getSelectedNode
();
if
(
node
&& !
node
.
isLeaf
()) {
sm
.
select
(
node
);
node
.
collapse
.
defer
(
1
,
node
, [
true
]);
}
}},{
// Ctrl + N = New Directory
key
:
78
,
ctrl
:
true
,
scope
:
this
,
stopEvent
:
true
,
fn
:
function
(
key
,
e
) {
var
sm
,
node
;
sm
=
this
.
getSelectionModel
();
node
=
sm
.
getSelectedNode
();
if
(
node
&&
this
.
enableNewDir
&&
this
.
readOnly
!==
true
) {
node
=
node
.
isLeaf
()
? node
.
parentNode
:
node
;
this
.
createNewDir
(
node
);
}
}}]
// }}}
});
// eo apply
// }}}
// {{{
// create loader
if
(!
this
.
loader
) {
this
.
loader
=
new
Ext
.
tree
.
TreeLoader
({
url
:
this
.
url
,
baseParams
:{
cmd
:
'get'
}
,
listeners
:{
beforeload
:{
scope
:
this
,
fn
:
function
(
loader
,
node
) {
loader
.
baseParams
.
path
=
this
.
getPath
(
node
);
}}
}
});
}
// }}}
// {{{
// install top menu if configured
if
(
true
===
this
.
topMenu
) {
this
.
tbar
= [{
text
:
this
.
fileText
,
disabled
:
true
,
scope
:
this
,
menu
:
this
.
getContextMenu
()
}];
}
// }}}
// call parent
Ext
.
ux
.
FileTreePanel
.
superclass
.
initComponent
.
apply
(
this
,
arguments
);
// {{{
// install treeEditor event handlers
if
(
this
.
treeEditor
) {
// do not enter edit mode on selected node click
this
.
treeEditor
.
beforeNodeClick
=
function
(
node
,
e
){
return true
;};
// treeEditor event handlers
this
.
treeEditor
.
on
({
complete
:{
scope
:
this
,
fn
:
this
.
onEditComplete
}
,
beforecomplete
:{
scope
:
this
,
fn
:
this
.
onBeforeEditComplete
}
});
}
// }}}
// {{{
// install event handlers
this
.
on
({
contextmenu
:{
scope
:
this
,
fn
:
this
.
onContextMenu
,
stopEvent
:
true
}
,
dblclick
:{
scope
:
this
,
fn
:
this
.
onDblClick
}
,
beforenodedrop
:{
scope
:
this
,
fn
:
this
.
onBeforeNodeDrop
}
,
nodedrop
:{
scope
:
this
,
fn
:
this
.
onNodeDrop
}
,
nodedragover
:{
scope
:
this
,
fn
:
this
.
onNodeDragOver
}
});
// }}}
// {{{
// add events
this
.
addEvents
(
/**
* @event beforeopen
* Fires before file open. Return false to cancel the event
* @param {Ext.ux.FileTreePanel} this
* @param {String} fileName name of the file being opened
* @param {String} url url of the file being opened
* @param {String} mode open mode
*/
'beforeopen'
/**
* @event open
* Fires after file open has been initiated
* @param {Ext.ux.FileTreePanel} this
* @param {String} fileName name of the file being opened
* @param {String} url url of the file being opened
* @param {String} mode open mode
*/
,
'open'
/**
* @event beforerename
* Fires after the user completes file name editing
* but before the file is renamed. Return false to cancel the event
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} node being renamed
* @param {String} newPath including file name
* @param {String} oldPath including file name
*/
,
'beforerename'
/**
* @event rename
* Fires after the file has been successfully renamed
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} node that has been renamed
* @param {String} newPath including file name
* @param {String} oldPath including file name
*/
,
'rename'
/**
* @event renamefailure
* Fires after a failure when renaming file
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} node rename of which failed
* @param {String} newPath including file name
* @param {String} oldPath including file name
*/
,
'renamefailure'
/**
* @event beforedelete
* Fires before a file or directory is deleted. Return false to cancel the event.
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} node being deleted
*/
,
'beforedelete'
/**
* @event delete
* Fires after a file or directory has been deleted
* @param {Ext.ux.FileTreePanel} this
* @param {String} path including file name that has been deleted
*/
,
'delete'
/**
* @event deletefailure
* Fires if node delete failed
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} node delete of which failed
*/
,
'deletefailure'
/**
* @event beforenewdir
* Fires before new directory is created. Return false to cancel the event
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} node under which the new directory is being created
*/
,
'beforenewdir'
/**
* @event newdir
* Fires after the new directory has been successfully created
* @param {Ext.ux.FileTreePanel} this
* @param {Ext.tree.AsyncTreeNode} new node/directory that has been created
*/
,
'newdir'
/**
* @event newdirfailure
* Fires if creation of new directory failed
* @param {Ext.ux.FileTreePanel} this
* @param {String} path creation of which failed
*/
,
'newdirfailure'
);
// eo addEvents
// }}}
}
// eo function initComponent
// }}}
// {{{
/**
* onRender override - just expands root node if configured
* @private
*/
,
onRender
:
function
() {
// call parent
Ext
.
ux
.
FileTreePanel
.
superclass
.
onRender
.
apply
(
this
,
arguments
);
if
(
true
===
this
.
topMenu
) {
this
.
topMenu
=
Ext
.
getCmp
(
this
.
getTopToolbar
().
items
.
itemAt
(
0
).
id
);
this
.
getSelectionModel
().
on
({
scope
:
this
,
selectionchange
:
function
(
sm
,
node
) {
var
disable
=
node ?
false
:
true
;
disable
=
disable
||
this
.
readOnly
;
this
.
topMenu
.
setDisabled
(
disable
);
}
});
Ext
.
apply
(
this
.
topMenu
, {
showMenu
:
function
() {
this
.
showContextMenu
(
false
);
}.
createDelegate
(
this
)
// ,menu:this.getContextMenu()
});
}
// expand root node if so configured
if
(
this
.
expandOnRender
) {
this
.
root
.
expand
();
}
// prevent default browser context menu to appear
this
.
el
.
on
({
contextmenu
:{
fn
:
function
(){
return false
;},
stopEvent
:
true
}
});
// setup loading mask if configured
if
(
true
===
this
.
loadMask
) {
this
.
loader
.
on
({
scope
:
this
.
el
,
beforeload
:
this
.
el
.
mask
.
createDelegate
(
this
.
el
, [
this
.
loadingText
+
'...'
])
,
load
:
this
.
el
.
unmask
,
loadexception
:
this
.
el
.
unmask
});
}
}
// eo function onRender
// }}}
// new methods
// {{{
/**
* runs after an Ajax requested command has completed/failed
* @private
* @param {Object} options Options used for the request
* @param {Boolean} success true if ajax call was successful (cmd may have failed)
* @param {Object} response ajax call response object
*/
,
cmdCallback
:
function
(
options
,
success
,
response
) {
var
i
,
o
,
node
;
var
showMsg
=
true
;
// process Ajax success
if
(
true
===
success
) {
// try to decode JSON response
try
{
o
=
Ext
.
decode
(
response
.
responseText
);
}
catch
(
ex
) {
this
.
showError
(
response
.
responseText
);
}
// process command success
if
(
true
===
o
.
success
) {
switch
(
options
.
params
.
cmd
) {
case
'delete'
:
if
(
true
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'delete'
,
this
,
this
.
getPath
(
options
.
node
));
}
options
.
node
.
parentNode
.
removeChild
(
options
.
node
);
break
;
case
'newdir'
:
if
(
true
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'newdir'
,
this
,
options
.
node
);
}
break
;
case
'rename'
:
this
.
updateCls
(
options
.
node
,
options
.
params
.
oldname
);
if
(
true
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'rename'
,
this
,
options
.
node
,
options
.
params
.
newname
,
options
.
params
.
oldname
);
}
break
;
}
}
// eo process command success
// process command failure
else
{
switch
(
options
.
params
.
cmd
) {
case
'rename'
:
// handle drag & drop rename error
if
(
options
.
oldParent
) {
options
.
oldParent
.
appendChild
(
options
.
node
);
}
// handle simple rename error
else
{
options
.
node
.
setText
(
options
.
oldName
);
}
// signal failure to onNodeDrop
if
(
options
.
e
) {
options
.
e
.
failure
=
true
;
}
if
(
true
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'renamefailure'
,
this
,
options
.
node
,
options
.
params
.
newname
,
options
.
params
.
oldname
);
}
break
;
case
'newdir'
:
if
(
false
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'newdirfailure'
,
this
,
options
.
params
.
dir
);
}
options
.
node
.
parentNode
.
removeChild
(
options
.
node
);
break
;
case
'delete'
:
if
(
true
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'deletefailure'
,
this
,
options
.
node
);
}
options
.
node
.
parentNode
.
reload
.
defer
(
1
,
options
.
node
.
parentNode
);
break
;
default
:
this
.
root
.
reload
();
break
;
}
// show default message box with server error
this
.
showError
(
o
.
error
||
response
.
responseText
);
}
// eo process command failure
}
// eo process Ajax success
// process Ajax failure
else
{
this
.
showError
(
response
.
responseText
);
}
}
// eo function cmdCallback
// }}}
// {{{
/**
* displays overwrite confirm msg box and runs passed callback if response is yes
* @private
* @param {String} filename File to overwrite
* @param {Function} callback Function to call on yes response
* @param {Object} scope Scope for callback (defaults to this)
*/
,
confirmOverwrite
:
function
(
filename
,
callback
,
scope
) {
Ext
.
Msg
.
show
({
title
:
this
.
confirmText
,
msg
:
String
.
format
(
this
.
existsText
,
filename
) +
'. '
+
this
.
overwriteText
,
icon
:
Ext
.
Msg
.
QUESTION
,
buttons
:
Ext
.
Msg
.
YESNO
,
fn
:
callback
.
createDelegate
(
scope
||
this
)
});
}
// }}}
// {{{
/**
* creates new directory (node)
* @private
* @param {Ext.tree.AsyncTreeNode} node
*/
,
createNewDir
:
function
(
node
) {
// fire beforenewdir event
if
(
true
!==
this
.
eventsSuspended
&&
false
===
this
.
fireEvent
(
'beforenewdir'
,
this
,
node
)) {
return
;
}
var
treeEditor
=
this
.
treeEditor
;
var
newNode
;
// get node to append the new directory to
var
appendNode
=
node
.
isLeaf
()
? node
.
parentNode
:
node
;
// create new folder after the appendNode is expanded
appendNode
.
expand
(
false
,
false
,
function
(
n
) {
// create new node
newNode
=
n
.
appendChild
(
new
Ext
.
tree
.
AsyncTreeNode
({
text
:
this
.
newdirText
,
iconCls
:
'folder'
}));
// setup one-shot event handler for editing completed
treeEditor
.
on
({
complete
:{
scope
:
this
,
single
:
true
,
fn
:
this
.
onNewDir
}}
);
// creating new directory flag
treeEditor
.
creatingNewDir
=
true
;
// start editing after short delay
(
function
(){
treeEditor
.
triggerEdit
(
newNode
);}.
defer
(
10
));
// expand callback needs to run in this context
}.
createDelegate
(
this
));
}
// eo function creatingNewDir
// }}}
// {{{
/**
* deletes the passed node
* @private
* @param {Ext.tree.AsyncTreeNode} node
*/
,
deleteNode
:
function
(
node
) {
// fire beforedelete event
if
(
true
!==
this
.
eventsSuspended
&&
false
===
this
.
fireEvent
(
'beforedelete'
,
this
,
node
)) {
return
;
}
Ext
.
Msg
.
show
({
title
:
this
.
deleteText
,
msg
:
this
.
reallyWantText
+
' '
+
this
.
deleteText
.
toLowerCase
() +
' <b>'
+
node
.
text
+
'</b>?'
,
icon
:
Ext
.
Msg
.
WARNING
,
buttons
:
Ext
.
Msg
.
YESNO
,
scope
:
this
,
fn
:
function
(
response
) {
// do nothing if answer is not yes
if
(
'yes'
!==
response
) {
this
.
getEl
().
dom
.
focus
();
return
;
}
// setup request options
var
options
= {
url
:
this
.
deleteUrl
||
this
.
url
,
method
:
this
.
method
,
scope
:
this
,
callback
:
this
.
cmdCallback
,
node
:
node
,
params
:{
cmd
:
'delete'
,
file
:
this
.
getPath
(
node
)
}
};
Ext
.
Ajax
.
request
(
options
);
}
});
}
// eo function deleteNode
// }}}
// {{{
/**
* requests file download from server
* @private
* @param {String} path Full path including file name but relative to server root path
*/
,
downloadFile
:
function
(
path
) {
// create hidden target iframe
var
id
=
Ext
.
id
();
var
frame
=
document
.
createElement
(
'iframe'
);
frame
.
id
=
id
;
frame
.
name
=
id
;
frame
.
className
=
'x-hidden'
;
if
(
Ext
.
isIE
) {
frame
.
src
=
Ext
.
SSL_SECURE_URL
;
}
document
.
body
.
appendChild
(
frame
);
if
(
Ext
.
isIE
) {
document
.
frames
[
id
].
name
=
id
;
}
var
form
=
Ext
.
DomHelper
.
append
(
document
.
body
, {
tag
:
'form'
,
method
:
'post'
,
action
:
this
.
downloadUrl
||
this
.
url
,
target
:
id
});
document
.
body
.
appendChild
(
form
);
var
hidden
;
// append cmd to form
hidden
=
document
.
createElement
(
'input'
);
hidden
.
type
=
'hidden'
;
hidden
.
name
=
'cmd'
;
hidden
.
value
=
'download'
;
form
.
appendChild
(
hidden
);
// append path to form
hidden
=
document
.
createElement
(
'input'
);
hidden
.
type
=
'hidden'
;
hidden
.
name
=
'path'
;
hidden
.
value
=
path
;
form
.
appendChild
(
hidden
);
var
callback
=
function
() {
Ext
.
EventManager
.
removeListener
(
frame
,
'load'
,
callback
,
this
);
setTimeout
(
function
() {
document
.
body
.
removeChild
(
form
);},
100
);
setTimeout
(
function
() {
document
.
body
.
removeChild
(
frame
);},
110
);
};
Ext
.
EventManager
.
on
(
frame
,
'load'
,
callback
,
this
);
form
.
submit
();
}
// }}}
// {{{
/**
* returns (and lazy create) the context menu
* @private
*/
,
getContextMenu
:
function
() {
// lazy create context menu
if
(!
this
.
contextmenu
) {
var
config
= {
singleUpload
:
this
.
singleUpload
,
maxFileSize
:
this
.
maxFileSize
,
enableProgress
:
this
.
enableProgress
};
if
(
this
.
baseParams
) {
config
.
baseParams
=
this
.
baseParams
;
}
this
.
contextmenu
=
new
Ext
.
ux
.
FileTreeMenu
(
config
);
this
.
contextmenu
.
on
({
click
:{
scope
:
this
,
fn
:
this
.
onContextClick
}});
this
.
uploadPanel
=
this
.
contextmenu
.
getItemByCmd
(
'upload-panel'
).
component
;
this
.
uploadPanel
.
on
({
beforeupload
:{
scope
:
this
,
fn
:
this
.
onBeforeUpload
}
,
allfinished
:{
scope
:
this
,
fn
:
this
.
onAllFinished
}
});
this
.
uploadPanel
.
setUrl
(
this
.
uploadUrl
||
this
.
url
);
}
return this
.
contextmenu
;
}
// eo function getContextMenu
// }}}
// {{{
/**
* returns file class based on name extension
* @private
* @param {String} name File name to get class of
*/
,
getFileCls
:
function
(
name
) {
var
atmp
=
name
.
split
(
'.'
);
if
(
1
===
atmp
.
length
) {
return this
.
fileCls
;
}
else
{
return this
.
fileCls
+
'-'
+
atmp
.
pop
().
toLowerCase
();
}
}
// }}}
// {{{
/**
* returns path of node (file/directory)
* @private
*/
,
getPath
:
function
(
node
) {
var
path
,
p
,
a
;
// get path for non-root node
if
(
node
!==
this
.
root
) {
p
=
node
.
parentNode
;
a
= [
node
.
text
];
while
(
p
&&
p
!==
this
.
root
) {
a
.
unshift
(
p
.
text
);
p
=
p
.
parentNode
;
}
a
.
unshift
(
this
.
root
.
attributes
.
path
||
''
);
path
=
a
.
join
(
this
.
pathSeparator
);
}
// path for root node is it's path attribute
else
{
path
=
node
.
attributes
.
path
||
''
;
}
// a little bit of security: strip leading / or .
// full path security checking has to be implemented on server
path
=
path
.
replace
(/
^
[
\
/
\
.]
*/
,
''
);
return
path
;
}
// eo function getPath
// }}}
// {{{
/**
* returns true if node has child with the specified name (text)
* @private
* @param {Ext.data.Node} node
* @param {String} childName
*/
,
hasChild
:
function
(
node
,
childName
) {
return
(
node
.
isLeaf
()
? node
.
parentNode
:
node
).
findChild
(
'text'
,
childName
) !==
null
;
}
// }}}
// {{{
/**
* Hides context menu
* @return {Ext.ux.FileTreeMenu} this
*/
,
hideContextMenu
:
function
() {
if
(
this
.
contextmenu
&&
this
.
contextmenu
.
isVisible
()) {
this
.
contextmenu
.
hide
();
}
return this
;
}
// eo function hideContextMenu
// }}}
// {{{
/**
* called before editing is completed - allows edit cancellation
* @private
* @param {TreeEditor} editor
* @param {String} newName
* @param {String} oldName
*/
,
onBeforeEditComplete
:
function
(
editor
,
newName
,
oldName
) {
if
(
editor
.
cancellingEdit
) {
editor
.
cancellingEdit
=
false
;
return
;
}
var
oldPath
=
this
.
getPath
(
editor
.
editNode
);
var
newPath
=
oldPath
.
replace
(/
\
/[
^\\
]+
$
/,
'/'
+
newName
);
if
(
false
===
this
.
fireEvent
(
'beforerename'
,
this
,
editor
.
editNode
,
newPath
,
oldPath
)) {
editor
.
cancellingEdit
=
true
;
editor
.
cancelEdit
();
return false
;
}
}
// }}}
// {{{
/**
* runs before node is dropped
* @private
* @param {Object} e dropEvent object
*/
,
onBeforeNodeDrop
:
function
(
e
) {
// source node, node being dragged
var
s
=
e
.
dropNode
;
// destination node (dropping on this node)
var
d
=
e
.
target
.
leaf ? e
.
target
.
parentNode
:
e
.
target
;
// node has been dropped within the same parent
if
(
s
.
parentNode
===
d
) {
return false
;
}
// check if same name exists in the destination
// this works only if destination node is loaded
if
(
this
.
hasChild
(
d
,
s
.
text
) &&
undefined
===
e
.
confirmed
) {
this
.
confirmOverwrite
(
s
.
text
,
function
(
response
) {
e
.
confirmed
=
'yes'
===
response
;
this
.
onBeforeNodeDrop
(
e
);
});
return false
;
}
if
(
false
===
e
.
confirmed
) {
return false
;
}
e
.
confirmed
=
undefined
;
e
.
oldParent
=
s
.
parentNode
;
var
oldName
=
this
.
getPath
(
s
);
var
newName
=
this
.
getPath
(
d
) +
'/'
+
s
.
text
;
// fire beforerename event
if
(
true
!==
this
.
eventsSuspended
&&
false
===
this
.
fireEvent
(
'beforerename'
,
this
,
s
,
newName
,
oldName
)) {
return false
;
}
var
options
= {
url
:
this
.
renameUrl
||
this
.
url
,
method
:
this
.
method
,
scope
:
this
,
callback
:
this
.
cmdCallback
,
node
:
s
,
oldParent
:
s
.
parentNode
,
e
:
e
,
params
:{
cmd
:
'rename'
,
oldname
:
oldName
,
newname
:
newName
}
};
Ext
.
Ajax
.
request
(
options
);
return true
;
}
// }}}
// {{{
/**
* sets uploadPanel's destination path
* @private
*/
,
onBeforeUpload
:
function
(
uploadPanel
) {
var
menu
=
this
.
getContextMenu
();
var
path
=
this
.
getPath
(
menu
.
node
);
if
(
menu
.
node
.
isLeaf
()) {
path
=
path
.
replace
(/
\
/[
^\
/]+
$
/,
''
,
path
);
}
uploadPanel
.
setPath
(
path
);
}
// eo function onBeforeUpload
// }}}
// {{{
/**
* reloads tree node on upload finish
* @private
*/
,
onAllFinished
:
function
(
uploader
) {
var
menu
=
this
.
getContextMenu
();
(
menu
.
node
.
isLeaf
()
? menu
.
node
.
parentNode
:
menu
.
node
).
reload
();
}
// eo function onAllFinished
// }}}
// {{{
/**
* @private
* context menu click handler
* @param {Ext.menu.Menu} context menu
* @param {Ext.menu.Item} item clicked
* @param {Ext.EventObject} raw event
*/
,
onContextClick
:
function
(
menu
,
item
,
e
) {
if
(
item
.
disabled
) {
return
;
}
var
node
=
menu
.
node
;
if
(!
node
) {
node
=
menu
.
parentMenu
.
node
;
}
switch
(
item
.
cmd
) {
case
'reload'
:
node
.
reload
();
break
;
case
'expand'
:
node
.
expand
(
true
);
break
;
case
'collapse'
:
node
.
collapse
(
true
);
break
;
case
'open'
:
this
.
openNode
(
node
);
break
;
case
'open-self'
:
this
.
openNode
(
node
,
'_self'
);
break
;
case
'open-popup'
:
this
.
openNode
(
node
,
'popup'
);
break
;
case
'open-blank'
:
this
.
openNode
(
node
,
'_blank'
);
break
;
case
'open-dwnld'
:
this
.
openNode
(
node
,
'download'
);
break
;
case
'rename'
:
this
.
treeEditor
.
triggerEdit
(
node
);
break
;
case
'delete'
:
this
.
deleteNode
(
node
);
break
;
case
'newdir'
:
this
.
createNewDir
(
node
);
break
;
default
:
break
;
}
}
// eo function onContextClick
// }}}
// {{{
/**
* contextmenu event handler
* @private
*/
,
onContextMenu
:
function
(
node
,
e
) {
if
(
this
.
readOnly
) {
return false
;
}
this
.
showContextMenu
(
node
);
return false
;
}
// eo function onContextMenu
// }}}
// {{{
/**
* dblclick handlers
* @private
*/
,
onDblClick
:
function
(
node
,
e
) {
this
.
openNode
(
node
);
}
// eo function onDblClick
// }}}
// {{{
/**
* Destroys the FileTreePanel and sub-components
* @private
*/
,
onDestroy
:
function
() {
// destroy contextmenu
if
(
this
.
contextmenu
) {
this
.
contextmenu
.
purgeListeners
();
this
.
contextmenu
.
destroy
();
this
.
contextmenu
=
null
;
}
// destroy treeEditor
if
(
this
.
treeEditor
) {
this
.
treeEditor
.
purgeListeners
();
this
.
treeEditor
.
destroy
();
this
.
treeEditor
=
null
;
}
// remover reference to treeSorter
if
(
this
.
treeSorter
) {
this
.
treeSorter
=
null
;
}
// call parent
Ext
.
ux
.
FileTreePanel
.
superclass
.
onDestroy
.
call
(
this
);
}
// eo function onDestroy
// }}}
// {{{
/**
* runs when editing of a node (rename) is completed
* @private
* @param {Ext.Editor} editor
* @param {String} newName
* @param {String} oldName
*/
,
onEditComplete
:
function
(
editor
,
newName
,
oldName
) {
var
node
=
editor
.
editNode
;
if
(
newName
===
oldName
||
editor
.
creatingNewDir
) {
editor
.
creatingNewDir
=
false
;
return
;
}
var
path
=
this
.
getPath
(
node
.
parentNode
);
var
options
= {
url
:
this
.
renameUrl
||
this
.
url
,
method
:
this
.
method
,
scope
:
this
,
callback
:
this
.
cmdCallback
,
node
:
node
,
oldName
:
oldName
,
params
:{
cmd
:
'rename'
,
oldname
:
path
+
'/'
+
oldName
,
newname
:
path
+
'/'
+
newName
}
};
Ext
.
Ajax
.
request
(
options
);
}
// }}}
// {{{
/**
* create new directory handler
* @private
* runs after editing of new directory name is completed
* @param {Ext.Editor} editor
*/
,
onNewDir
:
function
(
editor
) {
var
path
=
this
.
getPath
(
editor
.
editNode
);
var
options
= {
url
:
this
.
newdirUrl
||
this
.
url
,
method
:
this
.
method
,
scope
:
this
,
node
:
editor
.
editNode
,
callback
:
this
.
cmdCallback
,
params
:{
cmd
:
'newdir'
,
dir
:
path
}
};
Ext
.
Ajax
.
request
(
options
);
}
// }}}
// {{{
/**
* called while dragging over, decides if drop is allowed
* @private
* @param {Object} dd event
*/
,
onNodeDragOver
:
function
(
e
) {
e
.
cancel
=
e
.
target
.
disabled
||
e
.
dropNode
.
parentNode
===
e
.
target
.
parentNode
&&
e
.
target
.
isLeaf
();
}
// eo function onNodeDragOver
// }}}
// {{{
/**
* called when node is dropped
* @private
* @param {Object} dd event
*/
,
onNodeDrop
:
function
(
e
) {
// failure can be signalled by cmdCallback
// put drop node to the original parent in that case
if
(
true
===
e
.
failure
) {
e
.
oldParent
.
appendChild
(
e
.
dropNode
);
return
;
}
// if we already have node with the same text, remove the duplicate
var
sameNode
=
e
.
dropNode
.
parentNode
.
findChild
(
'text'
,
e
.
dropNode
.
text
);
if
(
sameNode
&&
sameNode
!==
e
.
dropNode
) {
sameNode
.
parentNode
.
removeChild
(
sameNode
);
}
}
// }}}
// {{{
/**
* Opens node
* @param {Ext.tree.AsyncTreeNode} node
* @param {String} mode Can be "_self", "_blank", or "popup". Defaults to (this.openMode)
*/
,
openNode
:
function
(
node
,
mode
) {
if
(!
this
.
enableOpen
) {
return
;
}
mode
=
mode
||
this
.
openMode
;
var
url
;
var
path
;
if
(
node
.
isLeaf
()) {
path
=
this
.
getPath
(
node
);
url
=
this
.
hrefPrefix
+
path
+
this
.
hrefSuffix
;
// fire beforeopen event
if
(
true
!==
this
.
eventsSuspended
&&
false
===
this
.
fireEvent
(
'beforeopen'
,
this
,
node
.
text
,
url
,
mode
)) {
return
;
}
switch
(
mode
) {
case
'popup'
:
if
(!
this
.
popup
||
this
.
popup
.
closed
) {
this
.
popup
=
window
.
open
(
url
,
this
.
hrefTarget
,
this
.
popupFeatures
);
}
this
.
popup
.
location
=
url
;
if
(
this
.
focusPopup
) {
this
.
popup
.
focus
();
}
break
;
case
'_self'
:
window
.
location
=
url
;
break
;
case
'_blank'
:
window
.
open
(
url
);
break
;
case
'download'
:
this
.
downloadFile
(
path
);
break
;
}
// fire open event
if
(
true
!==
this
.
eventsSuspended
) {
this
.
fireEvent
(
'open'
,
this
,
node
.
text
,
url
,
mode
);
}
}
}
// }}}
// {{{
/**
* Sets/Unsets delete of files/directories disabled/enabled
* @param {Boolean} disabled
* @return {Ext.ux.FileTreePanel} this
*/
,
setDeleteDisabled
:
function
(
disabled
) {
disabled
= !(!
disabled
);
if
(!
this
.
enableDelete
===
disabled
) {
return this
;
}
this
.
hideContextMenu
();
this
.
enableDelete
= !
disabled
;
}
// eo function setDeleteDisabled
// }}}
// {{{
/**
* Sets/Unsets creation of new directory disabled/enabled
* @param {Boolean} disabled
* @return {Ext.ux.FileTreePanel} this
*/
,
setNewdirDisabled
:
function
(
disabled
) {
disabled
= !(!
disabled
);
if
(!
this
.
enableNewDir
===
disabled
) {
return this
;
}
this
.
hideContextMenu
();
this
.
enableNewDir
= !
disabled
;
}
// eo function setNewdirDisabled
// }}}
// {{{
/**
* Sets/Unsets open files disabled/enabled
* @param {Boolean} disabled
* @return {Ext.ux.FileTreePanel} this
*/
,
setOpenDisabled
:
function
(
disabled
) {
disabled
= !(!
disabled
);
if
(!
this
.
enableOpen
===
disabled
) {
return this
;
}
this
.
hideContextMenu
();
this
.
enableOpen
= !
disabled
;
return this
;
}
// eo function setOpenDisabled
// }}}
// {{{
/**
* Sets/Unsets this tree to/from readOnly state
* @param {Boolean} readOnly
* @return {Ext.ux.FileTreePanel} this
*/
,
setReadOnly
:
function
(
readOnly
) {
readOnly
= !(!
readOnly
);
if
(
this
.
readOnly
===
readOnly
) {
return this
;
}
this
.
hideContextMenu
();
if
(
this
.
dragZone
) {
this
.
dragZone
.
locked
=
readOnly
;
}
this
.
readOnly
=
readOnly
;
return this
;
}
// eo function setReadOnly
// }}}
// {{{
/**
* Sets/Unsets rename of files/directories disabled/enabled
* @param {Boolean} disabled
* @return {Ext.ux.FileTreePanel} this
*/
,
setRenameDisabled
:
function
(
disabled
) {
disabled
= !(!
disabled
);
if
(!
this
.
enableRename
===
disabled
) {
return this
;
}
this
.
hideContextMenu
();
if
(
this
.
dragZone
) {
this
.
dragZone
.
locked
=
disabled
;
}
this
.
enableRename
= !
disabled
;
return this
;
}
// eo function setRenameDisabled
// }}}
// {{{
/**
* Sets/Unsets uploading of files disabled/enabled
* @param {Boolean} disabled
* @return {Ext.ux.FileTreePanel} this
*/
,
setUploadDisabled
:
function
(
disabled
) {
disabled
= !(!
disabled
);
if
(!
this
.
enableUpload
===
disabled
) {
return this
;
}
this
.
hideContextMenu
();
this
.
enableUpload
= !
disabled
;
return this
;
}
// of function setUploadDisabled
// }}}
// {{{
/**
* adjusts context menu depending on many things and shows it
* @private
* @param {Ext.tree.AsyncTreeNode} node Node on which was right-clicked
*/
,
showContextMenu
:
function
(
node
) {
// setup node alignment
var
topAlign
=
false
;
var
alignEl
=
this
.
topMenu ?
this
.
topMenu
.
getEl
() :
this
.
body
;
if
(!
node
) {
node
=
this
.
getSelectionModel
().
getSelectedNode
();
topAlign
=
true
;
}
else
{
alignEl
=
node
.
getUI
().
getEl
();
}
if
(!
node
) {
return
;
}
var
menu
=
this
.
getContextMenu
();
menu
.
node
=
node
;
// set node name
menu
.
getItemByCmd
(
'nodename'
).
setText
(
Ext
.
util
.
Format
.
ellipsis
(
node
.
text
,
22
));
// enable/disable items depending on node clicked
menu
.
setItemDisabled
(
'open'
, !
node
.
isLeaf
());
menu
.
setItemDisabled
(
'reload'
,
node
.
isLeaf
());
menu
.
setItemDisabled
(
'expand'
,
node
.
isLeaf
());
menu
.
setItemDisabled
(
'collapse'
,
node
.
isLeaf
());
menu
.
setItemDisabled
(
'delete'
,
node
===
this
.
root
||
node
.
disabled
);
menu
.
setItemDisabled
(
'rename'
,
this
.
readOnly
||
node
===
this
.
root
||
node
.
disabled
);
menu
.
setItemDisabled
(
'newdir'
,
this
.
readOnly
|| (
node
.
isLeaf
()
? node
.
parentNode
.
disabled
:
node
.
disabled
));
menu
.
setItemDisabled
(
'upload'
,
node
.
isLeaf
()
? node
.
parentNode
.
disabled
:
node
.
disabled
);
menu
.
setItemDisabled
(
'upload-panel'
,
node
.
isLeaf
()
? node
.
parentNode
.
disabled
:
node
.
disabled
);
// show/hide logic
menu
.
getItemByCmd
(
'open'
).
setVisible
(
this
.
enableOpen
);
menu
.
getItemByCmd
(
'delete'
).
setVisible
(
this
.
enableDelete
);
menu
.
getItemByCmd
(
'newdir'
).
setVisible
(
this
.
enableNewDir
);
menu
.
getItemByCmd
(
'rename'
).
setVisible
(
this
.
enableRename
);
menu
.
getItemByCmd
(
'upload'
).
setVisible
(
this
.
enableUpload
);
menu
.
getItemByCmd
(
'upload-panel'
).
setVisible
(
this
.
enableUpload
);
menu
.
getItemByCmd
(
'sep-upload'
).
setVisible
(
this
.
enableUpload
);
menu
.
getItemByCmd
(
'sep-collapse'
).
setVisible
(
this
.
enableNewDir
||
this
.
enableDelete
||
this
.
enableRename
);
// select node
node
.
select
();
// show menu
if
(
topAlign
) {
menu
.
showAt
(
menu
.
getEl
().
getAlignToXY
(
alignEl
,
'tl-bl?'
));
}
else
{
menu
.
showAt
(
menu
.
getEl
().
getAlignToXY
(
alignEl
,
'tl-tl?'
, [
0
,
18
]));
}
}
// eo function
// }}}
// {{{
/**
* universal show error function
* @private
* @param {String} msg message
* @param {String} title title
*/
,
showError
:
function
(
msg
,
title
) {
Ext
.
Msg
.
show
({
title
:
title
||
this
.
errorText
,
msg
:
Ext
.
util
.
Format
.
ellipsis
(
msg
,
this
.
maxMsgLen
)
,
fixCursor
:
true
,
icon
:
Ext
.
Msg
.
ERROR
,
buttons
:
Ext
.
Msg
.
OK
,
minWidth
:
1200
>
String
(
msg
).
length
?
360
:
600
});
}
// eo function showError
// }}}
// {{{
/**
* updates class of leaf after rename
* @private
* @param {Ext.tree.AsyncTreeNode} node Node to update class of
* @param {String} oldName Name the node had before
*/
,
updateCls
:
function
(
node
,
oldName
) {
if
(
node
.
isLeaf
()) {
Ext
.
fly
(
node
.
getUI
().
iconNode
).
removeClass
(
this
.
getFileCls
(
oldName
));
Ext
.
fly
(
node
.
getUI
().
iconNode
).
addClass
(
this
.
getFileCls
(
node
.
text
));
}
}
// }}}
});
// eo extend
// register xtype
Ext
.
reg
(
'filetreepanel'
,
Ext
.
ux
.
FileTreePanel
);
// eof
Free web analytics, website statistics