client/autocomplete.xml
author Mark Hammond <mhammond@skippinet.com.au>
Mon, 20 Apr 2009 11:06:46 +1000
changeset 210 9056db5cd8ef1ea86be7232e338c0b3025f250c3
parent 26 215ac5fef05b56737aec6c460331e4498a66b601
permissions -rw-r--r--
fix error retries by passing the old revision info around

<?xml version="1.0" encoding="UTF-8"?>
<xbl:xbl
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:xbl="http://www.w3.org/ns/xbl">

  <xbl:binding id="autocomplete">
    <xbl:template>
      <div>
        <input id="entry" type="text" name="entryText" value="" />
        <span id="subject"></span><br/>
        <pre id="body"></pre>
      </div>
    </xbl:template>
    <xbl:resources>
      <xbl:style><![CDATA[

      ]]></xbl:style>
    </xbl:resources>
    <xbl:implementation><![CDATA[
      ({
        SELECTED_CLASS: "auco_selected",
        _autocompleters: [],
        addCompleter: function (aCompleter) {
          this._autocompleters.push(aCompleter);
        },
        _serial: 0,
        resultsByType: null,
        goFish: function (aText) {
          var serial = this._serial++;
          this.resultsByType = {};
          this._autocompleters.forEach(function (completer) {
            completer.complete(this, aText);
          }, this);
        },
        haveSomeResults: function(aText, aNodes, aCompleter, aPriority) {
          // bail if this is not the reslt for what is currently typed
          if (aText != this._entry.value)
            return;
          this.resultsByType[aCompleter.type] = [aPriority, aNodes];
          var nodes = [];
          var prioritizedResults = [];
          for each (var tupe in this.resultsByType)
            prioritizedResults.push(tupe);
          prioritizedResults.sort(function (a,b) { return b[0] - a[0]; });
          for (var i = 0; i < prioritizedResults.length; i++)
            nodes = nodes.concat(prioritizedResults[i][1]);
          
          this.clearResultsList();
          $(this._resultDiv).append(nodes);
          this.showResultsList();
        },
        /* ===== Results List Stuff ===== */
        _resultDiv: null,
        resultsVisible: false,
        showResultsList: function() {
          var entryOffset = $(this._entry).offset();
          $(this._resultDiv).css({
            left: entryOffset.let,
            top: entryOffset.top + this._entry.offsetHeight,
            width: $(this._entry).width()
          }).show();
          this.resultsVisible = true;
        },
        hideResultsList: function() {
          $(this._resultDiv).hide();
          this.resultsVisible = false;
        },
        clearResultsList: function() {
          $(this._resultDiv).empty();
          this.selectedIndex = null;
        },
        selectedIndex: null,
        setSelectedIndex: function(aDesiredIndex) {
          var children = this._resultDiv.childNodes;
          if (children.length == 0)
            aDesiredIndex = null;
          else if (aDesiredIndex === undefined)
            aDesiredIndex = 0;
          else if (aDesiredIndex < 0)
            aDesiredIndex = 0;
          else if (aDesiredIndex >= children.length)
            aDesiredIndex = children.length - 1;
            
          if (aDesiredIndex == this.selectedIndex)
            return;
          
          if (this.selectedIndex != null)
            $(children[this.selectedIndex]).removeClass(this.SELECTED_CLASS);
          this.selectedIndex = aDesiredIndex;
          if (this.selectedIndex != null)
            $(children[this.selectedIndex]).addClass(this.SELECTED_CLASS);
        },
        /* ===== Action ===== */
        actionListener: null,
        addActionListener: function(aActionListener) {
          this.actionListener = aActionListener;
        },
        actOnSelected: function() {
          console.log("Taking action on index", this.selectedIndex);
          if (this.selectedIndex != null) {
            var selected = this._resultDiv.childNodes[this.selectedIndex];
            if (this.actionListener != null)
              this.actionListener(selected);
            else
              console.log("Warning! There was no action listener!", this, this.actionListener);
          }
          console.log("Actiong taking done!");
        },
        
        /* ===== House-keeping ==== */
        xblBindingAttached: function () {
          console.log("autocompleter binding and what not", this);
          this._entry = this.shadowTree.getElementById("entry");
          this._resultDiv = $("<div/>").hide().css("position", "absolute")
                             .addClass("auco_results")
                             .appendTo(document.body)[0];
          console.log("done binding");
        },
      })
    ]]></xbl:implementation>
    <xbl:handlers>
      <xbl:handler event="textInput"><![CDATA[
        var text = this._entry.value + event.data;
        this.goFish(text);
      ]]></xbl:handler>
      <xbl:handler event="keydown"><![CDATA[
        if (event.keyIdentifier == "Enter") {
          this.actOnSelected();
          this.hideResultsList();
        }
        else if (event.keyIdentifier == "Up") {
          if (this.selectedIndex == 0)
            this.setSelectedIndex(null);
          else
            this.setSelectedIndex(this.selectedIndex - 1);
        }
        else if (event.keyIdentifier == "Down") {
          // if the results aren't currently visible, show them and go to the
          //  first index
          if (!this.resultsVisible) {
            this.showResultsList();
            this.setSelectedIndex(0);
            return;
          }
          if (this.selectedIndex == null)
            this.setSelectedIndex(0);
          else
            this.setSelectedIndex(this.selectedIndex + 1);
        }
        else if (event.keyIdentifier == "U+001B") { // escape
          this.hideResultsList();
        }
      ]]></xbl:handler>
      <xbl:handler event="blur"><![CDATA[
      ]]></xbl:handler>

    </xbl:handlers>
  </xbl:binding>
  <!-- Contact -->
  <xbl:binding id="contact-completion">
    <xbl:template>
      <img id="picture" class="contactpic"/>
      <span id="name"></span><br />
      <span id="emails"></span>
    </xbl:template>
    <xbl:resources>
      <xbl:style><![CDATA[
        .contactpic {
          width: 32;
          height: 32;
          float: left;
        }
      ]]></xbl:style>
    </xbl:resources>
    <xbl:implementation><![CDATA[
      ({
        type: "contact",
        getType: function() {
          return this.type;
        },
        contact: null,
        getContact: function() {
          return this.contact;
        },
        setContact: function(aContact) {
          this.contact = aContact;
          console.log("setContact", this);
          this._nName.textContent = this.contact.name;
          var bestEmail = null;
          var emailText = this.contact.identities.map(function (identity) {
            if (identity.kind == "email")
              bestEmail = identity.value;
            return identity.value;
          }).join(", ");
          this._nEmails.textContent = emailText;
          if (bestEmail) {
            this._nPicture.setAttribute("src",
              "http://www.gravatar.com/avatar/" + hex_md5(bestEmail) +
              ".jpg?r=pg&d=identicon&s=32");
            // XXX soon, let's point at the gravatar
            // XXX later, let's use a pic if it exists, or gravatar push if it
            //  doesn't.  (or maybe have a daemon that takes care of it?)
          }
        },
        xblBindingAttached: function () {
          console.log("attaching contact result");
          this._nPicture = this.shadowTree.getElementById("picture");
          this._nName = this.shadowTree.getElementById("name");
          this._nEmails = this.shadowTree.getElementById("emails");
          console.log("done attaching contact result");
        },
      })
    ]]></xbl:implementation>
    <xbl:handlers>
    </xbl:handlers>
  </xbl:binding>
  <!-- Tag -->
  <xbl:binding id="tag-completion">
    <xbl:template>
      messages tagged <span id="tagname"/>
    </xbl:template>
    <xbl:implementation><![CDATA[
      ({
        type: "tag",
        getType: function() {
          return this.type;
        },
        tagname: null,
        getTagName: function() {
          return this.tagname;
        },
        setTagName: function(aTagName) {
          this.tagname = aTagName;
          this.shadowTree.getElementById("tagname").textContent = aTagName;
        }
      })
    ]]></xbl:implementation>
    <xbl:handlers>
    </xbl:handlers>
  </xbl:binding>

</xbl:xbl>