FileBrowser.class.js

Summary

Client-part of FileBrowser component

Version: SVN Snapshot

Author: Kamil Dudka


Class Summary
FileBrowser  

/**
 * @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;
  }
}


Documentation generated by JSDoc on Sat Mar 8 10:26:45 2008