Adding preferences to an extension

  • Revision slug: Adding_preferences_to_an_extension
  • Revision title: Adding preferences to an extension
  • Revision id: 31560
  • Created:
  • Creator: Nickolay
  • Is current revision? No
  • Comment /* Startup */

Revision Content

Introduction

This article takes the Creating a dynamic status bar extension sample to the next level, adding a popup menu that lets you quickly switch between multiple stocks to watch. It also adds a preference dialog that lets you switch a a stock other than one of the ones included in the popup menu.

As before, concepts covered in the previous articles in this series won't be rehashed here, so if you haven't already seen them:

Download the sample

You can download a copy of this sample to look over, or to use as the basis for your own extension. We won't be rehashing how to update the manifests for this sample, since the changes are minor.

Download the sample

Update the manifests

The install manifest and chrome manifest need to be updated. By and large, the changes are simply changing the ID of the extension. However, we do need to add one new line to the install.rdf file:

 <em:optionsURL>chrome://stockwatcher2/content/options.xul</em:optionsURL>

This line establishes the URL of the XUL file that describes the preference dialog.

Establish the defaults

In order to set a default preference for the stock to monitor, we need to add a new folder to our extension's package, called "defaults", which in turn contains another folder called "preferences". Inside that, we create a file, defaults.js, that describes the default value of our preferences:

 pref("stockwatcher2.symbol", "GOOG");

To learn more about the preference system, read Preferences API.

The JavaScript code

In order to monitor changes to our preferences, we need to install an observer using the nsIPrefBranch2 interface. To do that, we need to reimplement our code into an object.

That involves turning each function into a member of the StockWatcher class. Let's take a look at each function in the class.

Startup

The Startup() function is called when our extension is first loaded. Its job is to start up the observer to watch for changes to our preferences, instantiate an object to use to manager our preferences, and install a timeout handler to install the interval routine to update the stock information periodically.

var StockWatcher = {
  // ...
  startup: function()
  {
    // Register to receive notifications of preference changes
    this.prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefService)
                           .getBranch("stockwatcher2.");
    this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
    this.prefs.addObserver("", this, false);
    
    this.tickerSymbol = this.prefs.getCharPref("symbol").toUpperCase();
    
    // Set up for first update; the rest will be done using an interval
    window.setTimeout(function() { this.installIntervalHandler() }, 1000);
  },

This code starts by adding an observer on the "stockwatcher2." branch using the nsIPrefBranch2 interface.

To do this, we first get the preferences service component and use it to get the branch with our extension's preferences. After that we add an observer using the addObserver method of nsIPrefBranch2 interface. The observer directs notifications to the StockWatcher object.

We then get the current value of the "symbol" preference using the getCharPref(). It is the ticker symbol to monitor.

Finally, we set a timeout to handle installing the interval handler in about one second.

Shutdown

The Shutdown() routine deactivates the observer on the preferences. This is where we would add any other shutdown tasks we need to perform.

 Shutdown: function()
 {
   Components.classes["@mozilla.org/preferences-service;1"].getService
       (Components.interfaces.nsIPrefBranchInternal).removeObserver
       ("stockwatcher2.", this);
 },

observe

The observe() function is called when events occur on the preferences object.

 observe: function(subject, topic, data)
 {
   if (topic != "nsPref:changed")
   {
     return;
   }
 
   switch(data)
   {
     case "stockwatcher2.symbol":
       tickerSymbol = StockWatcher.prefs.getCharPref("symbol").toUpperCase();
       StockWatcher.refreshInformation();
       break;
   }
 },

If the topic is nsPref:changed, we know that the notification indicates that a preference on our extension has changed. If it's the symbol preference, we grab the updated value from the preferences object and call our own refreshInformation() routine, so that the new stock symbol's information is fetched immediately.

watchStock

While we're at it, let's add a function that sets which stock we want to be watching, changing the preference and immediately requesting a refresh of the display.

 watchStock: function(newSymbol)
 {
   StockWatcher.tickerSymbol = newSymbol;
   StockWatcher.prefs.setCharPref("symbol", newSymbol);
   StockWatcher.refreshInformation();
 },

The only new information for us here is the call to the preference object's setCharPref function, which sets the value of the "symbol" preference.

Everything else

The rest of the functions do things we've already seen before, so we won't dwell on them.

The only thing left to do is to install the event listeners needed to run the Startup() and Shutdown() routines automatically when our extension is loaded and unloaded.

 window.addEventListener("load", function(e) { StockWatcher.Startup(); }, false);
 window.addEventListener("unload", function(e) { StockWatcher.Shutdown(); }, false);

Design the preference dialog

Now that we've written all the code, we need to build the XUL file for the preference dialog.

 <?xml version="1.0"?>
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <prefwindow id="stockwatcher2-prefs"
     title="StockWatcher 2 Options"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
 <preferences>
   <preference id="symbol" name="stockwatcher2.symbol" type="string"/>
 </preferences>
 
 <prefpane id="sw2-stock-pane" label="Stock Settings">
   <vbox>
     <hbox align="center">
       <label control="symbol" value="Stock to watch: "/>
       <textbox preference="symbol" id="symbol" maxlength="4"/>
     </hbox>
   </vbox>
 </prefpane>
 
 </prefwindow>

The <preferences> block establishes all the settings we implement as well as their types. In our case, we have a single preference, the stock symbol to monitor. Preferences are identified by name; in this case, the name is "stockwatcher2.symbol".

The actual user interface is described in the <prefpane> block. The <VBOX> and <HBOX> elements are used to lay out the user interface. The former establishes the vertical orientation of widgets, while the latter arranges them horizontally.

Our dialog has two widgets in it. The first is a label describing the textbox. The second is the textbox itself, in which the user enters the symbol. The preference property ties the textbox to the "symbol" preference. This lets the preference value automatically be updated to reflect the content of the textbox.

Adding the context menu

Adding the contextual menu is easy; all the work that needs doing is done in the stockwatcher2.xul file. The first step is to add the oncontext property to the status bar panel:

 <statusbar id="status-bar">
   <statusbarpanel id="stockwatcher2"
     label="Loading..."
     context="stockmenu"
     onclick="StockWatcher.refreshInformation()"
   />
 </statusbar>

Now when the user clicks on the status bar panel, the stock information refreshes, but when they right-click on it, a context menu pops up.

Defining the menu is also easy. All we need to do is create a popupset describing the menu, as follows:

 <popupset>
   <popup id="stockmenu" position="after_start">
     <menuitem label="Refresh Now" oncommand="StockWatcher.refreshInformation()"/>
     <menuseparator/>
     <menuitem label="Apple (AAPL)" oncommand="StockWatcher.watchStock('AAPL')"/>
     <menuitem label="Google (GOOG)" oncommand="StockWatcher.watchStock('GOOG')"/>
     <menuitem label="Microsoft (MSFT)" oncommand="StockWatcher.watchStock('MSFT')"/>
     <menuitem label="Yahoo! (YHOO)" oncommand="StockWatcher.watchStock('YHOO')"/>
   </popup>
 </popupset>

Each item in the menu has a label property, which specifies the text displayed in the menu, as well as an oncommand property, which indicates the JavaScript code to execute when the user selects that item.

The Refresh Now option calls the StockWatcher.refreshInformation() function, to refresh the display. The rest of the options call the StockWatcher.watchStock() function to start watching a different stock.

For a more detailed tutorial on creating popup menus, see XUL Tutorial:Popup Menus.

Revision Source

<h2 name="Introduction">Introduction</h2>
<p>This article takes the <a href="en/Creating_a_dynamic_status_bar_extension">Creating a dynamic status bar extension</a> sample to the next level, adding a popup menu that lets you quickly switch between multiple stocks to watch.  It also adds a preference dialog that lets you switch a a stock other than one of the ones included in the popup menu.
</p><p>As before, concepts covered in the previous articles in this series won't be rehashed here, so if you haven't already seen them:
</p>
<ul><li> <a href="en/Creating_a_status_bar_extension">Creating a status bar extension</a>
</li><li> <a href="en/Creating_a_dynamic_status_bar_extension">Creating a dynamic status bar extension</a>
</li></ul>
<h2 name="Download_the_sample">Download the sample</h2>
<p>You can download a copy of this sample to look over, or to use as the basis for your own extension.  We won't be rehashing how to update the manifests for this sample, since the changes are minor.
</p><p><a class="external" href="http://developer.mozilla.org/samples/extension-samples/stockwatcher2.zip">Download the sample</a>
</p>
<h2 name="Update_the_manifests">Update the manifests</h2>
<p>The install manifest and <a href="en/Chrome">chrome</a> manifest need to be updated.  By and large, the changes are simply changing the ID of the extension.  However, we do need to add one new line to the <code>install.rdf</code> file:
</p>
<pre class="eval"> <span class="plain">&lt;em:optionsURL&gt;chrome://stockwatcher2/content/options.xul&lt;/em:optionsURL&gt;</span>
</pre>
<p>This line establishes the URL of the XUL file that describes the preference dialog.
</p>
<h3 name="Establish_the_defaults">Establish the defaults</h3>
<p>In order to set a default preference for the stock to monitor, we need to add a new folder to our extension's package, called "defaults", which in turn contains another folder called "preferences".  Inside that, we create a file, <code>defaults.js</code>, that describes the default value of our preferences:
</p>
<pre class="eval"> pref("stockwatcher2.symbol", "GOOG");
</pre>
<p>To learn more about the preference system, read <a href="en/Preferences_API">Preferences API</a>.
</p>
<h2 name="The_JavaScript_code">The JavaScript code</h2>
<p>In order to monitor changes to our preferences, we need to install an observer using the <a href="en/NsIPrefBranch2">nsIPrefBranch2</a> interface.  To do that, we need to reimplement our code into an object.
</p><p>That involves turning each function into a member of the <code>StockWatcher</code> class.  Let's take a look at each function in the class.
</p>
<h3 name="Startup">Startup</h3>
<p>The <code>Startup()</code> function is called when our extension is first loaded.  Its job is to start up the observer to watch for changes to our preferences, instantiate an object to use to manager our preferences, and install a timeout handler to install the interval routine to update the stock information periodically.
</p>
<pre class="eval">var StockWatcher = {
  // ...
  startup: function()
  {
    // Register to receive notifications of preference changes
    this.prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefService)
                           .getBranch("stockwatcher2.");
    this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
    this.prefs.addObserver("", this, false);
    
    this.tickerSymbol = this.prefs.getCharPref("symbol").toUpperCase();
    
    // Set up for first update; the rest will be done using an interval
    window.setTimeout(function() { this.installIntervalHandler() }, 1000);
  },
</pre>
<p>This code starts by adding an observer on the "stockwatcher2." branch using the <code>nsIPrefBranch2</code> interface.
</p><p>To do this, we first get the preferences service component and use it to get the branch with our extension's preferences. After that we add an observer using the <code>addObserver</code> method of <code>nsIPrefBranch2</code> interface. The observer directs notifications to the <code>StockWatcher</code> object.
</p><p>We then get the current value of the "symbol" preference using the <code>getCharPref()</code>. It is the ticker symbol to monitor.
</p><p>Finally, we set a timeout to handle installing the interval handler in about one second.
</p>
<h3 name="Shutdown">Shutdown</h3>
<p>The <code>Shutdown()</code> routine deactivates the observer on the preferences.  This is where we would add any other shutdown tasks we need to perform.
</p>
<pre class="eval"> Shutdown: function()
 {
   Components.classes["@mozilla.org/preferences-service;1"].getService
       (Components.interfaces.nsIPrefBranchInternal).removeObserver
       ("stockwatcher2.", this);
 },
</pre>
<h3 name="observe">observe</h3>
<p>The <code>observe()</code> function is called when events occur on the preferences object.
</p>
<pre class="eval"> observe: function(subject, topic, data)
 {
   if (topic != "nsPref:changed")
   {
     return;
   }
 
   switch(data)
   {
     case "stockwatcher2.symbol":
       tickerSymbol = StockWatcher.prefs.getCharPref("symbol").toUpperCase();
       StockWatcher.refreshInformation();
       break;
   }
 },
</pre>
<p>If the topic is <code>nsPref:changed</code>, we know that the notification indicates that a preference on our extension has changed.  If it's the symbol preference, we grab the updated value from the preferences object and call our own <code>refreshInformation()</code> routine, so that the new stock symbol's information is fetched immediately.
</p>
<h3 name="watchStock">watchStock</h3>
<p>While we're at it, let's add a function that sets which stock we want to be watching, changing the preference and immediately requesting a refresh of the display.
</p>
<pre class="eval"> watchStock: function(newSymbol)
 {
   StockWatcher.tickerSymbol = newSymbol;
   StockWatcher.prefs.setCharPref("symbol", newSymbol);
   StockWatcher.refreshInformation();
 },
</pre>
<p>The only new information for us here is the call to the preference object's <code>setCharPref</code> function, which sets the value of the "symbol" preference.
</p>
<h3 name="Everything_else">Everything else</h3>
<p>The rest of the functions do things we've already seen before, so we won't dwell on them.
</p><p>The only thing left to do is to install the event listeners needed to run the <code>Startup()</code> and <code>Shutdown()</code> routines automatically when our extension is loaded and unloaded.
</p>
<pre class="eval"> window.addEventListener("load", function(e) { StockWatcher.Startup(); }, false);
 window.addEventListener("unload", function(e) { StockWatcher.Shutdown(); }, false);
</pre>
<h2 name="Design_the_preference_dialog">Design the preference dialog</h2>
<p>Now that we've written all the code, we need to build the XUL file for the preference dialog.
</p>
<pre class="eval"> &lt;?xml version="1.0"?&gt;
 
 &lt;?xml-stylesheet href="<span class="plain">chrome://global/skin/</span>" type="text/css"?&gt;
 
 &lt;prefwindow id="stockwatcher2-prefs"
     title="StockWatcher 2 Options"
     xmlns="<span class="plain">http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul</span>"&gt;
 
 &lt;preferences&gt;
   &lt;preference id="symbol" name="stockwatcher2.symbol" type="string"/&gt;
 &lt;/preferences&gt;
 
 &lt;prefpane id="sw2-stock-pane" label="Stock Settings"&gt;
   &lt;vbox&gt;
     &lt;hbox align="center"&gt;
       &lt;label control="symbol" value="Stock to watch: "/&gt;
       &lt;textbox preference="symbol" id="symbol" maxlength="4"/&gt;
     &lt;/hbox&gt;
   &lt;/vbox&gt;
 &lt;/prefpane&gt;
 
 &lt;/prefwindow&gt;
</pre>
<p>The <code>&lt;preferences&gt;</code> block establishes all the settings we implement as well as their types.  In our case, we have a single preference, the stock symbol to monitor.  Preferences are identified by name; in this case, the name is "stockwatcher2.symbol".
</p><p>The actual user interface is described in the <code>&lt;prefpane&gt;</code> block.  The <code>&lt;VBOX&gt;</code> and <code>&lt;HBOX&gt;</code> elements are used to lay out the user interface.  The former establishes the vertical orientation of widgets, while the latter arranges them horizontally.
</p><p>Our dialog has two widgets in it.  The first is a label describing the textbox.  The second is the textbox itself, in which the user enters the symbol.  The <code>preference</code> property ties the textbox to the "symbol" preference.  This lets the preference value automatically be updated to reflect the content of the textbox.
</p>
<h3 name="Adding_the_context_menu">Adding the context menu</h3>
<p>Adding the contextual menu is easy; all the work that needs doing is done in the <code>stockwatcher2.xul</code> file.  The first step is to add the <code>oncontext</code> property to the status bar panel:
</p>
<pre class="eval"> &lt;statusbar id="status-bar"&gt;
   &lt;statusbarpanel id="stockwatcher2"
     label="Loading..."
     context="stockmenu"
     onclick="StockWatcher.refreshInformation()"
   /&gt;
 &lt;/statusbar&gt;
</pre>
<p>Now when the user clicks on the status bar panel, the stock information refreshes, but when they right-click on it, a context menu pops up.
</p><p>Defining the menu is also easy.  All we need to do is create a <code>popupset</code> describing the menu, as follows:
</p>
<pre class="eval"> &lt;popupset&gt;
   &lt;popup id="stockmenu" position="after_start"&gt;
     &lt;menuitem label="Refresh Now" oncommand="StockWatcher.refreshInformation()"/&gt;
     &lt;menuseparator/&gt;
     &lt;menuitem label="Apple (AAPL)" oncommand="StockWatcher.watchStock('AAPL')"/&gt;
     &lt;menuitem label="Google (GOOG)" oncommand="StockWatcher.watchStock('GOOG')"/&gt;
     &lt;menuitem label="Microsoft (MSFT)" oncommand="StockWatcher.watchStock('MSFT')"/&gt;
     &lt;menuitem label="Yahoo! (YHOO)" oncommand="StockWatcher.watchStock('YHOO')"/&gt;
   &lt;/popup&gt;
 &lt;/popupset&gt;
</pre>
<p>Each item in the menu has a <code>label</code> property, which specifies the text displayed in the menu, as well as an <code>oncommand</code> property, which indicates the JavaScript code to execute when the user selects that item.
</p><p>The Refresh Now option calls the <code>StockWatcher.refreshInformation()</code> function, to refresh the display.  The rest of the options call the <code>StockWatcher.watchStock()</code> function to start watching a different stock.
</p><p>For a more detailed tutorial on creating popup menus, see <a href="en/XUL_Tutorial/Popup_Menus">XUL Tutorial:Popup Menus</a>.
</p>
Revert to this revision