mozilla

Revision 138057 of Fun With XBL and XPConnect

  • Revision slug: Fun_With_XBL_and_XPConnect
  • Revision title: Fun With XBL and XPConnect
  • Revision id: 138057
  • Created:
  • Creator: Andreas Wuest
  • Is current revision? No
  • Comment adapted image link

Revision Content

Introduction

This article describes an application of XBL in which a simple XPCOM interface is made accessible to a XUL widget. The interface definition feature of XBL is used to define an interface through XPConnect to a C++ object that does auto completion. Once the regular XUL textfield widget is bound to this interface, it calls the auto complete function of the object using regular JavaScript. The basic model of interaction is as follows:

Image:xpconnect_textfield.png

Binding to the XPCOM Object

The widget holds onto an XPCOM object that is the auto complete engine that will perform our auto complete lookups.

<binding name="autocomplete" extends="xul:box">
  <content>
    <xul:textfield class="addressingWidget"/>
    <xul:menupopup/>
  </content>
        
  <interface>
    <property name="autoCompleteSession">
      ![CDATA[                                 
       Components.classes['component://netscape/messenger/autocomplete&type=addrbook'].
        getService(Components.interfaces.nsIAutoCompleteSession);
      ]]
    </property>

So we've defined a property on the widget called autoCompleteSession. The initial value of this property evaluates into an xp-connect object. Now calls to .autoCompleteSession will return the xp-connect object.

Exposing the XPCOM Interfaces

One somewhat nasty trick you need to do is to manually expose the interfaces from the XPCOM object that you want your widget to support. Fortunately, for this demonstration we only have one method on support. Fortunately, for this demonstration we only have one method on the interface =). All I'm doing is specifically defining a method attribute on the xbl widget which forwards the method call to the XPCOM object.

<method name="autoComplete">
  <argument name="aSearchString"/>
  <argument name="resultListener"/>
  <body>
    ![CDATA[
      return this.autoCompleteSession.autoComplete(null, 
        anonymousContent[0], aSearchString, this.autoCompleteListener);
     ]]
  </body>
</method>

You can see that the body of the method is just getting the auto complete session and calling the auto complete method on it.

Implementing a Widget Interface

The next thing I needed to do was to implement an interface on the widget which I could then pass into the auto complete session. I wanted the auto complete session to call back into the widget with search results. I can do a similar trick to what I did for the XPCOM object:

<property name="autoCompleteListener">
  ![CDATA[
   ({
     onAutoCompleteResult: function(aItem, aOriginalString, aMatch)
       {
         if ( aItem )
         {
           anonymousContent[0].value = aMatch;
         }
       }
    })
   ]]
</property>

As long as the JS for the value of autoCompleteListener evaluates into an object (and wrapping the expression in a set of parens like I did, does this) then the value of autoCompleteListener is an object that implements my interface. Now I can pass the result of .autoCompleteListener into methods that require an auto complete listener (like my auto complete session object).

Creating the Event Handler

The last part is the easy part. I wanted a handler that would kick off the auto complete search. The handler calls the auto complete method we've exposed on the widget which in turn forwards the call to the XPCOM object, passing in our implementation of nsIAutoCompleteListener.

<handlers>
  <handler type="keypress" keycode="vk_return"
    value="autoComplete(anonymousContent[0].value,  
      this.autoCompleteListener);"/>
</handlers>

Revision Source

<p>
</p>
<h2 name="Introduction"> Introduction </h2>
<p>This article describes an application of XBL in which a simple XPCOM interface is made accessible to a XUL widget. The interface definition feature of XBL is used to define an interface through XPConnect to a C++ object that does auto completion. Once the regular XUL textfield widget is bound to this interface, it calls the auto complete function of the object using regular JavaScript. The basic model of interaction is as follows:
</p><p><img alt="Image:xpconnect_textfield.png" src="File:en/Media_Gallery/Xpconnect_textfield.png">
</p>
<h2 name="Binding_to_the_XPCOM_Object"> Binding to the XPCOM Object </h2>
<p>The widget holds onto an XPCOM object that is the auto complete 
engine that will perform our auto complete lookups.

</p><pre>&lt;binding name="autocomplete" extends="xul:box"&gt;
  &lt;content&gt;
    &lt;xul:textfield class="addressingWidget"/&gt;
    &lt;xul:menupopup/&gt;
  &lt;/content&gt;
        
  &lt;interface&gt;
    &lt;property name="autoCompleteSession"&gt;
      ![CDATA[                                 
       Components.classes['component://netscape/messenger/autocomplete&amp;type=addrbook'].
        getService(Components.interfaces.nsIAutoCompleteSession);
      ]]
    &lt;/property&gt;
</pre>
<p>So we've defined a property on the widget called autoCompleteSession. 
The initial value of this property evaluates into an xp-connect object. 
Now calls to .autoCompleteSession will return the xp-connect object.
</p>
<h2 name="Exposing_the_XPCOM_Interfaces"> Exposing the XPCOM Interfaces </h2>
<p>One somewhat nasty trick you need to do is to manually expose the 
interfaces from the XPCOM object that you want your widget to 
support. Fortunately, for this demonstration we only have one method on
support. Fortunately, for this demonstration we only have one method on 
the interface =). All I'm doing is specifically defining a method 
attribute on the xbl widget which forwards the method call to the 
XPCOM object.
</p>
<pre>&lt;method name="autoComplete"&gt;
  &lt;argument name="aSearchString"/&gt;
  &lt;argument name="resultListener"/&gt;
  &lt;body&gt;
    ![CDATA[
      return this.autoCompleteSession.autoComplete(null, 
        anonymousContent[0], aSearchString, this.autoCompleteListener);
     ]]
  &lt;/body&gt;
&lt;/method&gt;
</pre>
<p>You can see that the body of the method is just getting the auto 
complete session and calling the auto complete method on it.
</p>
<h2 name="Implementing_a_Widget_Interface"> Implementing a Widget Interface </h2>
<p>The next thing I needed to do was to implement an interface on the 
widget which I could then pass into the auto complete session. I wanted 
the auto complete session to call back into the widget with search 
results. I can do a similar trick to what I did for the XPCOM object:
</p>
<pre>&lt;property name="autoCompleteListener"&gt;
  ![CDATA[
   ({
     onAutoCompleteResult: function(aItem, aOriginalString, aMatch)
       {
         if ( aItem )
         {
           anonymousContent[0].value = aMatch;
         }
       }
    })
   ]]
&lt;/property&gt;
</pre>
<p>As long as the JS for the value of autoCompleteListener evaluates into 
an object (and wrapping the expression in a set of parens like I did, 
does this) then the value of autoCompleteListener is an object that 
implements my interface. Now I can pass the result of 
.autoCompleteListener into methods that require an auto complete 
listener (like my auto complete session object).
</p>
<h2 name="Creating_the_Event_Handler"> Creating the Event Handler </h2>
<p>The last part is the easy part. I wanted a handler that would kick 
off the auto complete search. The handler calls the auto complete method 
we've exposed on the widget which in turn forwards the call to the 
XPCOM object, passing in our implementation of nsIAutoCompleteListener.
</p>
<pre>&lt;handlers&gt;
  &lt;handler type="keypress" keycode="vk_return"
    value="autoComplete(anonymousContent[0].value,  
      this.autoCompleteListener);"/&gt;
&lt;/handlers&gt;
</pre>
Revert to this revision