Web components
File detail
Source code
/**
* @fileoverview Client-part of FileBrowser component
* @author Kamil Dudka
* @version SVN Snapshot
*/
/**
* @constructor
* @param fbWorkspace Xml ID of TBODY element inside FileBrowser file-list table.
*/
function FileBrowser(fbWorkspace) {
/**
* Image source for expanded list item. See server-part documentation of FileBrowser.
*/
this.imgExpanded = '';
/**
* Image source for collapsed list item. See server-part documentation of FileBrowser.
*/
this.imgCollapsed = '';
/**
* Image source for list item without childern. See server-part documentation of FileBrowser.
*/
this.imgNoChildern = '';
/**
* Source of 1x1 transparent image used to work around bug in Opera 9.23. See server-part documentation of FileBrowser.
*/
this.imgAlfaPixel = '';
/**
* Tree nested node indentation step [px]. Default is 20px.
*/
this.pxIndent = 20;
/**
* Maximum size of async image preview [px]. Default is 320px.
* <br/><br/><b><i>The selected size must be supported by server part of FileBrowser!</i></b>
*/
this.pxImgPreview = 320;
/**
* CSS style of IMG element of async image preview.
* Some styles could not be defined in default.css
* due to problem problem browsers such as IE6
*/
this.styleImgPreview = '';
/**
* Margin to screen edge when moving preview to visible viewport [px].
* Default is 20px.
* <br/><br/><b><i>This property has no effect on IE.</i></b>
*/
this.pxImgPreviewMargin = 20;
if (null==fbWorkspace)
fbWorkspace = 'fbWorkspace';
/** @private **/ this.canvas = document.getElementById(fbWorkspace);
/** @private **/ this.xmlHttp = null;
/**
* Start FileBrowser functionality. FileBrowser properties should not be modified after call of this method.
*/
FileBrowser.prototype.init = function() {
// Create innerText DOM node property if not present.
patchInnerText();
this.xmlHttp = createXmlHttp();
if (null==this.xmlHttp)
// Failed to create XMLHttp object
return;
var rowEnumerator = new ChildElementsEnumerator(this.canvas, 'tr');
var row;
while (null!= (row = rowEnumerator.next()))
// Init already displayed rows
this.initNode(row, 0);
}
/**
* Update table visual chessboard.
* There should be no reasen to call this method outside FileBrowser class.
*/
FileBrowser.prototype.updateVisual = function() {
var rowEnumerator = new ChildElementsEnumerator(this.canvas, 'tr');
var row;
var rowNumber = 0;
while (null!= (row = rowEnumerator.next()))
if (row.style.display!='none')
row.className = rowNumber++%2?'odd':'even';
}
/**
* Internal use only for now.
* Collapse tree node.
*/
FileBrowser.prototype.collapse = function() {
// Hide child nodes
var stack = this.fb_row.fb_childList.concat();
while (0!=stack.length) {
var current = stack.pop();
stack = stack.concat(current.fb_childList);
current.style.display = 'none';
}
// Update current node
this.src = this.fb.imgCollapsed;
this.alt = '+';
this.fb_row.fb_isExpanded = false;
}
/**
* Internal use only for now.
* Expand tree node.
*/
FileBrowser.prototype.expand = function() {
// Enumerate childern
var list = this.fb_row.fb_childList;
for (i in list) {
var current = list[i]
current.style.display = '';
if (current.fb_isExpanded) {
// Expand child node recursively
var td = (new ChildElementsEnumerator(current, 'td')).next();
var img = (new ChildElementsEnumerator(td, 'img')).next();
this.fb.expand.call(img);
}
}
// Update current node
this.src = this.fb.imgExpanded;
this.alt = '-';
this.fb_row.fb_isExpanded = true;
}
/**
* @private
* XMLHttp object callback
*/
FileBrowser.prototype.onAsyncResponse = function() {
var xmlHttp = this.fb.xmlHttp;
if (xmlHttp.readyState!=4)
// Dispatch not ready
return;
var asyncResponse;
try {
if (xmlHttp.status!=200)
throw new Error('Negative server response');
var doc = document.createElement('response');
if (-1==navigator.appName.search(/konqueror/i))
doc.innerHTML = xmlHttp.responseText;
else
// Special version for Konqueror
doc = document.importNode(xmlHttp.responseXML.documentElement, true);
// Look up for TBODY element
var list = doc.getElementsByTagName('tbody');
if (list.length!=1)
throw new Error('Corrupted data');
asyncResponse = list[0];
}
catch (e) {
var jsLog = document.getElementById('jsLog');
if (null==jsLog)
// No way to debug silently
return;
var p = document.createElement('p');
p.innerHTML = '<b>Exception caught:</b> ' + e.message;
jsLog.appendChild(p);
return;
}
// Update current node
this.fb_row.fb_isDownloaded = true;
this.fb_row.fb_isExpanded = true;
this.src = this.fb.imgExpanded;
this.alt = '-';
if (0==asyncResponse.getElementsByTagName('tr').length) {
// Empty directory
this.onclick = null;
return;
}
// Look up for row after current
var rowEnumerator = new ChildElementsEnumerator(this.fb.canvas, 'tr');
if (null==this.fb_row)
// avoid infinite loop
return;
var rowAfter;
while (this.fb_row!= (rowAfter = rowEnumerator.next()));
rowAfter = rowEnumerator.next();
// Parse response
var rowEnumerator = new ChildElementsEnumerator(asyncResponse, 'tr');
var row;
while (null!= (row = rowEnumerator.next())) {
var toAppend = row.cloneNode(true);
this.fb.initNode(toAppend, this.fb_row.fb_depth+1);
this.fb.canvas.insertBefore(toAppend, rowAfter);
this.fb_row.fb_childList.push(toAppend);
}
this.fb.updateVisual.call(this.fb);
}
/**
* @private
* Initialize tree node
* @param row TR element of tree node to initialize
* @param nodeDepth Node nest level (0 = no nest)
*/
FileBrowser.prototype.initNode = function(row, nodeDepth) {
// Initialize current node
row.fb_depth = nodeDepth;
row.fb_isDirectory = false;
row.fb_isDownloaded = false;
row.fb_isExpanded = false;
row.fb_childList = new Array();
// Look up for treeImg
var cellEnumerator = new ChildElementsEnumerator(row, 'td');
var imgList = cellEnumerator.next().getElementsByTagName('img');
if (imgList.length==0)
return;
var img = imgList[0];
// Look up for (file/dir) link
var textCell = cellEnumerator.next()
var linkList = textCell.getElementsByTagName('a');
if (0==linkList.length)
// Link not found
return;
var fileLink = linkList[0];
// Display image preview for images
fileLink.onmouseover = function(e) {
try {
var text = this.innerText;
if (
-1==text.search(/\.png/i) &&
-1==text.search(/\.jpe?g/i) &&
-1==text.search(/\.gif/i))
// Not an image
return;
// Display preview to DIV #fbImgPreview
var imgPreview = document.getElementById('fbImgPreview');
var imgSrc = this.getAttribute('href') + '?action=thumbnail' + this.fb.pxImgPreview;
imgPreview.innerHTML =
'<img src="' + imgSrc + '" alt="' + text + '" ' +
'style="' + this.fb.styleImgPreview + '"/>';
// Determinate current browser family
var bKonqueror = (-1!=navigator.appName.search(/konqueror/i));
var bFirefox = (-1!=navigator.appName.search(/netscape/i));
var bExplorer = (-1!=navigator.appName.search(/explorer/i));
if (-1!=navigator.userAgent.search(/opera/i))
// Patch for Opera 9.50 Alpha
bKonqueror = bFirefox = bExplorer = false;
/*if (bKonqueror || bFirefox) {
// Konqueror, FF
var img = imgPreview.getElementsByTagName('img')[0];
img.fb = this.fb;
// Place image to mouse position in Y axis
img.style.top = e.clientY + 'px';
// Move image to visible viewport after load
img.onload = function() {
var upLimit = function(limit, diff, value) {
return Math.min(value+diff, limit) - diff;
}
var top = this.style.top.replace(/px$/, '');
this.style.top = upLimit(
window.innerHeight,
this.clientHeight + this.fb.pxImgPreviewMargin,
parseInt(this.style.top.replace(/px$/, ''))
) + 'px';
}
} else*/ if (bExplorer) {
// IE
var img = imgPreview.getElementsByTagName('img')[0];
img.style.position = 'absolute';
var version = parseFloat(navigator.appVersion.split("MSIE")[1])
if (version < 5.5) {
// IE version not supported
img.style.display='none';
return;
}
var page = (version<7)?
document.body: // IE6, IE5.5
document.documentElement; // IE7
img.style.top = event.clientY + 20 + page.scrollTop + 'px';
img.style.left = event.clientX + 20 + page.scrollLeft + 'px';
}
}
catch (e) {
return;
}
}
// Remove image preview
fileLink.onmouseout = function() {
var imgPreview = document.getElementById('fbImgPreview');
// navigator.appName does not work on Opera 9.50 Alpha
if (-1==navigator.userAgent.search(/opera/i))
imgPreview.innerHTML = '';
else {
// Emergency solution for Opera.
// It shloud be romeved when Opera bug is fixed.
var boxSize = this.fb.pxImgPreview + this.fb.pxImgPreviewMargin;
imgPreview.innerHTML =
'<img src="' + this.fb.imgAlfaPixel +
'" alt="" width="' + boxSize + '" height="' + boxSize + '"/>';
var img = imgPreview.getElementsByTagName('img')[0];
img.style.zIndex = '-2';
}
}
fileLink.fb = this;
// Indent current node
// Indentation size is dependent on current node nest level.
var visualDepth = nodeDepth*this.pxIndent + 'px';
textCell.style.paddingLeft = visualDepth;
img.style.left = visualDepth;
// Check if row is directory or file
if (img.className == 'imgCollapsed')
row.fb_isDirectory = true;
else
return;
// Link to async data dispatcher
var asyncLink = fileLink.getAttribute('href') + '?action=asyncDispatch';
// Tree image onclick event for current node
img.onclick = function() {
// Wrapper for XMLHttp callback (static to non-static adapter)
var cbInternal = function() {
FileBrowser.prototype.onAsyncResponse.call(arguments.callee.img);
}
cbInternal.img = this;
if (this.fb_row.fb_isExpanded)
// Collapse current node
this.fb.collapse.call(this);
else if (this.fb_row.fb_isDownloaded)
// Expnad current node (no transfer needed)
this.fb.expand.call(this);
else {
// Request new data from server
var xmlHttp = this.fb.xmlHttp;
xmlHttp.open('GET', arguments.callee.fbAsyncLink, true);
xmlHttp.onreadystatechange = cbInternal;
xmlHttp.send(null);
}
this.fb.updateVisual.call(this.fb);
}
img.onclick.fbAsyncLink = asyncLink;
img.fb = this;
img.fb_row = row;
}
}