Labs/JS Modules: Difference between revisions
(describe the new StringBundle module) |
|||
Line 127: | Line 127: | ||
Note: in addition to the API described above, the StringBundle object supports the API for the <code>stringbundle</code> XBL binding to make it easier to switch from the binding to the module: | Note: in addition to the API described above, the StringBundle object supports the API for the <code>stringbundle</code> XBL binding to make it easier to switch from the binding to the module: | ||
<strike> | let strings = <strike>document.getElementById("myStringBundleElement");</strike> | ||
new StringBundle("chrome://example/locale/strings.properties"); | |||
let foo = strings.getString("foo"); | let foo = strings.getString("foo"); | ||
let barFormatted = strings.getFormattedString("bar", [arg1, arg2]); | let barFormatted = strings.getFormattedString("bar", [arg1, arg2]); | ||
Line 136: | Line 136: | ||
dump (string.key + " = " + string.value + "\n"); | dump (string.key + " = " + string.value + "\n"); | ||
} | } | ||
== URI == | == URI == |
Revision as of 06:45, 1 December 2008
Back to Labs.
Here you'll find a collection of modules which you can import into your extension. The goal is to make extension development easier by implementing common functionality as reusable libraries. If you would like to contribute a new module, get in touch with us at #labs!
Usage
The modules here are designed to be imported with Components.utils.import(). To avoid namespace collisions with core code and other extensions, use the two-parameter form of import() to import the module's symbols into a namespace defined by your extension, i.e.:
let MyExtension = {}; Components.utils.import("resource://myextension/modules/SomeModule.js", MyExtension);
Modules
Logging
This is a partial implementation of the Log4* interfaces (for example, see log4j or log4net). The original implementation came from Michael Johnston, but it was heavily modified by Dan Mills to get it into Weave.
To use it, create one or more appenders while initializing your add-on, and create loggers in the places where you have logging messages to emit. Both loggers and appenders have minimum log levels to allow you to customize your logging output without changing any code. For example:
function setupLogging() { // The basic formatter will output lines like: // DATE/TIME LoggerName LEVEL (log message) let formatter = new Log4Moz.BasicFormatter(); // Loggers are hierarchical, lowering this log level will affect all output let root = Log4Moz.Service.rootLogger; root.level = Log4Moz.Level["All"]; // A console appender outputs to the JS Error Console let capp = new Log4Moz.ConsoleAppender(formatter); capp.level = Log4Moz.Level["Warn"]; root.addAppender(capp); // A dump appender outputs to standard out let dapp = new Log4Moz.DumpAppender(formatter); dapp.level = Log4Moz.Level["Debug"]; root.addAppender(dapp); } // ... in some other code, but after setupLogging() has been done // Get a logger, give it a name unique to this chunk of code. // Use dots to create a hierarchy, this way you can later change // the log level of sets of loggers under some common root let logger = Log4Moz.Service.getLogger("MyExtension.MyClass"); logger.level = Log4Moz.Level["Debug"]; // Log some messages // Given our settings, the error would show up everywhere, but the // debug one would only show up in stdout logger.error("Oh noes!! Something bad happened!"); logger.debug("Details about bad thing only useful during debugging");
The implementation is much less comprehensive than what you might see in the log4* projects at Apache, however. For example, support for serializing the logging configuration or reading it in is non-existent. Also, note that this module used to be an XPCOM component and that influenced some of the interface design (e.g., Service.newAppender() instead of making a new Appender() directly). That will change in the future.
Get it here:
- log4moz.js - Adorned
- log4moz.js - Raw (right-click to save)
Observers
The Observers module provides an API for observing and sending notifications using the observer service.
You can register ordinary functions as observers:
function someFunction(subject, topic, data) { /* do something */ }; Observers.add(someFunction, "test"); Observers.remove(someFunction, "test");
You can also register objects that implement the nsIObserver interface as observers:
let someObject = { observe: function(subject, topic, data) { /* do something */ } }; Observers.add(someObject, "test"); Observers.remove(someObject, "test");
And you can send notifications whose subject is an arbitrary JavaScript object:
let someObject = { /* some properties & methods */ }; Observers.notify(someObject, "test", null);
Note: you can use this module to observe notifications generated by any XPCOM component, and you can send notifications whose subject is an XPCOM component, but only JavaScript consumers of this API can send and receive notifications whose subject is an arbitrary JavaScript object.
Preferences
The Preferences module provides an API for accessing application preferences. Getting and setting prefs is easy:
let foo = Preferences.get("extensions.test.foo"); Preferences.set("extensions.test.foo", foo);
As with FUEL's preferences API, datatypes are auto-detected, and you can pass a default value that the API will return if the pref doesn't have a value:
let foo = Preferences.get("extensions.test.nonexistent", "default value"); // foo == "default value"
Unlike FUEL, which returns null in the same situation, the module doesn't return a value when you get a nonexistent pref without specifying a default value:
let foo = Preferences.get("extensions.test.nonexistent"); // typeof foo == "undefined"
(Although the preferences service doesn't currently store null values, other interfaces like nsIVariant and nsIContentPrefService and embedded storage engines like SQLite distinguish between the null value and "doesn't have a value," as does JavaScript, so it seems more consistent and robust to do so here as well.)
Because we aren't using XPCOM, we can include some interesting API features. First, as you may have noticed already, the interface doesn't require you to create a branch just to get a pref, but you can create one if you want to via an intuitive syntax:
let testBranch = new Preferences("extensions.test."); // Preferences.get("extensions.test.foo") == testBranch.get("foo")
The get method uses polymorphism to enable you to retrieve multiple values in a single call, and, with JS 1.7's destructuring assignment, you can assign those values to individual variables:
let [foo, bar, baz] = testBranch.get(["foo", "bar", "baz"]);
And set
lets you update multiple prefs in one call (although they still get updated individually on the backend, so each change results in a separate notification):
testBranch.set({ foo: 1, bar: "awesome", baz: true });
StringBundle
The StringBundle module makes it easier to access string bundles from JS modules and XPCOM components. To use the module, import it, create a new instance of StringBundle, and then use the instance's get
and getAll
methods to retrieve strings (you can get both plain and formatted strings with get
):
let strings = new StringBundle("chrome://example/locale/strings.properties"); let foo = strings.get("foo"); let barFormatted = strings.get("bar", [arg1, arg2]); for each (let string in strings.getAll()) dump (string.key + " = " + string.value + "\n");
Note: in addition to the API described above, the StringBundle object supports the API for the stringbundle
XBL binding to make it easier to switch from the binding to the module:
let strings =document.getElementById("myStringBundleElement");new StringBundle("chrome://example/locale/strings.properties"); let foo = strings.getString("foo"); let barFormatted = strings.getFormattedString("bar", [arg1, arg2]); let enumerator = strings.strings; while (enumerator.hasMoreElements()) { let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement); dump (string.key + " = " + string.value + "\n"); }
URI
The URI module creates nsIURI objects from location strings. It wraps nsIIOService::newURI. There are two ways to use it. First, you can call it as a constructor:
let foo = new URI(spec, charset, baseURI);
Second, you can call its get method:
let bar = URI.get(spec, charset, baseURI);
In both cases, the parameters are the same, and only the spec parameter is required; the charset and baseURI parameters are optional. The difference between these two techniques is that the constructor throws an error if you pass it an invalid spec, while the get method returns null in that case.
In the future, the URI module may return JavaScript URI objects implementing a different interface than nsIURI.