/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * You may not modify, use, reproduce, or distribute this
 * software except in compliance with the terms of the License at:
 *
 *   http://developer.sun.com/berkeley_license.html
 *
 * $Id: script.js,v 1.13 2006/04/06 08:16:24 mattbohm Exp $
 */


/* =============================================================================
   Blueprints AJAX Components -- AutoCompleteComponent Functions
   ========================================================================== */  


dojo.require("dojo.io.*");


/**
 * Define our component container object (if necessary).
 */
if (bpui_undefined("autocomplete", bpui)) {

  bpui.autocomplete = {


    /**
     * Localizable messages in the default language.  Treat this variable
     * as an associative array keyed by a message identifier.  Robust
     * applications will dynamically replace these messages with a localized
     * version based on the locale of the current user.
     */
    messages : new Object(),


    /**
     * Holder for state information for the component that currently has focus.
     * This would need to be turned into an array if it were possible to have
     * more than one Auto Complete Text Field component with focus at the
     * same time.
     */
    state : new Object(),


    /**
     * The version number of this script.
     */
    version : {
      major: 0,
      minor: 1,
      patch: 0,
      flag: "",
      toString: function() {
        with (bpui.autocomplete.version) {
          return major + "." + minor + "." + patch + flag;
        }
      }
    }

  }

}


// Initialize the localizable messages for this component
bpui.autocomplete.messages["bindError"] = "An error occurred performing an asynchronous request";


// alert("Autocomplete script version is " + bpui.autocomplete.version.toString());


/**
 * Calculate and return the X coordinate at which we should draw the
 * completion list.
 *
 * @param element The <input type="text"> element the user is using
 */
bpui.autocomplete.getElementX = function(element) {

    var targetLeft = 0;
    while (element) {
      if (element.offsetParent) {
        targetLeft += element.offsetLeft;
      } else if (element.x) {
        targetLeft += element.x;
      }
      element = element.offsetParent;
    }
    return targetLeft;

}


/**
 * Calculate and return the Y coordinate at which we should draw the
 * completion list.
 *
 * @param element The <input type="text"> element the user is using
 */
bpui.autocomplete.getElementY = function(element) {

    var targetTop = 0;
    while (element) {
        if (element.offsetParent) {
            targetTop += element.offsetTop;
        } else if (element.y) {
            targetTop += element.y;
        }
        element = element.offsetParent;
    }
    return targetTop;

}


/**
 * Calculate and return the width we should use for the completion list.
 *
 * @param element The <input type="text"> element the user is using
 */
bpui.autocomplete.getWidth = function(element) {

    if (element.clientWidth && element.offsetWidth && element.clientWidth <element.offsetWidth) {
        return element.clientWidth; /* some mozillas (like 1.4.1) return bogus clientWidth so ensure it's in range */
    } else if (element.offsetWidth) {
        return element.offsetWidth;
    } else if (element.width) {
        return element.width;
    } else {
        return 0;
    }

}


/**
 * Start the autocompletion process and begin asynchronous communication
 * with the host on each keystroke.
 *
 * @param targetName DOM identifier of the <input type="text"> element
 *  the user is using
 * @param menuName DOM identifier of the <div> element we will use for
 *  the completions list
 * @param method Method binding expression passed to the server to retrieve
 * @param callback URL for our asynchronous completion method callbacks
 *  the appropriate completion choices
 * @param onchoose JavaScript function to execute when the user chooses
 *  one of the completion choices
 * @param ondisplay JavaScript function to execute when the server offers
 *  a completion choice
 */
bpui.autocomplete.startCompletion = function(targetName, menuName, method, callback, onchoose, ondisplay) {

    var target = document.getElementById(targetName);
    var menu = document.getElementById(menuName);
    menu.style.left = bpui.autocomplete.getElementX(target) + "px";
    menu.style.top = bpui.autocomplete.getElementY(target) + target.offsetHeight + 2 + "px";
    var width = bpui.autocomplete.getWidth(target);
    if (width > 0) {
        menu.style.width = width + "px";
    }

    bpui.autocomplete.state.menu = menu;
    bpui.autocomplete.state.onchoose = onchoose;
    bpui.autocomplete.state.ondisplay = ondisplay;
    bpui.autocomplete.state.targetName = targetName;

    var bindArgs = {
      url:      callback + "?method=" + escape(method) + "&prefix=" + escape(target.value),
      mimetype: "text/xml",
      error:    bpui.autocomplete.processError,
      load:     bpui.autocomplete.processLoad
    };
    dojo.io.bind(bindArgs);

}


/**
 * Respond to the user choosing a particular item from the completions list.
 *
 * @param targetName DOM id of the <input type="text"> field the user is using
 * @param item Completion item selected
 */
bpui.autocomplete.chooseItem = function(targetName, item) {

    if (!bpui.autocomplete.state.onchoose) {
        var target = document.getElementById(targetName);
        target.value = item;
    } else {
        bpui.autocomplete.state.onchoose(item);
    }

}


/**
 * Stop performing autocompletion, and hide the <div> element we used for it.
 *
 * @param menuName DOM identifier of the <div> element we will use for
 *  the completions list
 */
bpui.autocomplete.stopCompletion = function(menuName) {

    var menu = document.getElementById(menuName);
    if (menu != null) {
        bpui.autocomplete.clearItems(menu);
        menu.style.visibility = "hidden";
    }

}


/* Stop completion shortly.
   This is necessary because I want to stop completion from the blur
   (focus loss event) of the completion text field, but that will also
   happen, right BEFORE a link click in the completion dialog is processed.
   If this is done synchronously, the link is deleted before it is processed
   by stop completion. Therefore, I use the delayed variety which schedules
   stop completion instead such that the link is processed first.
*/
bpui.autocomplete.stopCompletionDelayed = function(menuName) {

    /* Would like to shorten timeout but this seems to trip up Safari */
    setTimeout("bpui.autocomplete.stopCompletion('" + menuName + "')", 400);

}


/**
 * Process the failure of an asynchronous response.
 *
 * @param type Event type ("error")
 * @param errorObject Low-level error object
 */
bpui.autocomplete.processError = function(type, errorObject) {

    window.status = bpui.autocomplete.messages["bindError"] + ":  " + errorObject;

}


/**
 * Process an asynchronous response when it is ready.
 *
 * @param type Event type ("load")
 * @param data Data returned with the response
 * @param event Low-level event object
 */
bpui.autocomplete.processLoad = function(type, data, event) {

    if (event.readyState == 4) {
        if (event.status == 200) {
          bpui.autocomplete.parseMessages(bpui.autocomplete.state.menu, data);
        } else {
          bpui.autocomplete.clearItems(bpui.autocomplete.state.menu);
        }
    }

}


/**
 * Parse the incoming response and create the completions list.
 *
 * @param menu The <div> element we are using for the completion list
 * @param data XML data to be parsed
 */
bpui.autocomplete.parseMessages = function(menu, data) {

    bpui.autocomplete.clearItems(menu);
    menu.style.visibility = "visible";
    var items = data.getElementsByTagName("item");

    if ((items != null) && (items.length > 0)) {
        for (var loop = 0; loop < items.length; loop++) {
            if (items[loop].childNodes != null && items[loop].childNodes.length > 0) {
                bpui.autocomplete.appendItem(menu, items[loop].childNodes[0].nodeValue);
            }
        }
    }
}


/**
 * Clear any existing completion items from the <div> we are using.
 *
 * @param menu The <div> element we are using for the completion list
 */
bpui.autocomplete.clearItems = function(menu) {

    if (menu) {
      for (var loop = menu.childNodes.length -1; loop >= 0 ; loop--) {
         menu.removeChild(menu.childNodes[loop]);
      }
    }

}


/**
 * Append a new item to the completions list.
 *
 * @param menu The <div> element we are using for the completion list
 * @param name The new value to be added
 */
bpui.autocomplete.appendItem = function(menu, name) {

    var item = document.createElement("div");
    menu.appendChild(item);
    var linkElement = document.createElement("a");
    linkElement.className = "popupItem";
    linkElement.href = "#";
    linkElement.onclick = function() {
        bpui.autocomplete.chooseItem(bpui.autocomplete.state.targetName, name); 
        bpui.autocomplete.stopCompletion();
        return false;
    }
    var displayName = name;
    if (bpui.autocomplete.state.ondisplay) {
        displayName = bpui.autocomplete.state.ondisplay(name);
    }
    var spanItem = document.createElement("span");
    spanItem.innerHTML = displayName;
    linkElement.appendChild(spanItem);
    item.appendChild(linkElement);

}



/* TODO: cache returned results for recent prefixes such that if users are backing
   up in the textfield we don't need to bother the server */
/* TODO: keyboard navigation, and automatically select best match. Insert this
   match in the textfield and select the untyped remainder */



