This article needs a technical review. How you can help.
Views are one component of the Places model-view-controller design. Use them to display nsINavHistoryResult
objects to the user. See Querying Places for information about obtaining and using nsINavHistoryResult
objects, which this page assumes you are familiar with.
An nsINavHistoryResult
instance provides the data for a view. The view is responsible for expanding the root nsINavHistoryContainerResultNode
of this instance and displaying the nsINavHistoryResultNode
objects contained therein.
The built-in views
If you need to show the contents of Bookmarks or History in your extension or application, you may want to use the built-in Places views, which are generic and will save you a lot of time writing basic functionality so that you can focus on building your extension or application.
Places provides the following built-in views:
Instantiating
The three built-in views are simply standard XUL elements with a special type
attribute whose value is "places".
Every XUL document containing a built-in view must import the stylesheet browser/components/places/content/places.css
and overlay the file browser/components/places/content/placesOverlay.xul
:
<?xml-stylesheet href="chrome://browser/content/places/places.css" ?> <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul" ?>
It's this stylesheet that binds elements with the special type
attribute to one of the views. The overlay contains JavaScript required by the views. It also contains the built-in Places context menu and commands, which you may want to take advantage of in your own uses of the views.
Connecting a view to its data
To hook up a built-in view to its data, use the view's special place
attribute.
Note: Starting in Gecko 2.0, you can't use the place
attribute with menus. See Menu view for details.
You may specify the attribute directly in the XUL or set its corresponding property via JavaScript. Its value is the URI of a query, and the data shown in the view are the results of this query. For simple queries whose URIs do not change over the life of the view, you might specify the place
attribute directly in the XUL. For more complicated queries or queries whose URIs you plan on changing, you will want to set the view's place
property dynamically using JavaScript. Note that in the latter case it is not sufficient to call setAttribute
on the element; use the element's place
property instead. See Querying Places and Places query URIs for information on query URIs.
The following example uses the built-in tree view to display bookmarks whose titles or URLs contain "mozilla". Remember that, because XUL is XML, any ampersands in query URIs must be written as entity reference &
and not simply &
.
<tree type="places" place="place:terms=mozilla&onlyBookmarked=1&queryType=1"> <treecols> <treecol id="title" label="My Bookmarks" flex="1" primary="true" /> </treecols> <treechildren /> </tree>
The next example does the same as the last but uses JavaScript to set the tree's place
attribute:
var histServ = Cc["@mozilla.org/browser/nav-history-service;1"]. getService(Ci.nsINavHistoryService); var query = histServ.getNewQuery(); query.searchTerms = "mozilla"; query.onlyBookmarked = true; var opts = histServ.getNewQueryOptions(); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; var uri = histServ.queriesToQueryString([query], 1, opts); var tree = document.getElementById("mytree"); tree.place = uri;
These two examples use the built-in tree view, but the point is to demonstrate the use of the place
attribute and property. In this regard the built-in menu and toolbar views are no different.
When a view's underlying data changes, the view will automatically update itself so that it displays the new data. This update is handled by logic between the view and its results. All Places views implement or own an instance of interface nsINavHistoryResultViewer
, which is the point of interaction between a view and its results. Results themselves observe Places changes, and if on a Places change a result determines that its data specifically has changed, it notifies its view by calling an appropriate method of nsINavHistoryResultViewer
. Once notified, the view is responsible for updating itself.
Note: Starting in Gecko 2.0, nsINavHistoryResultViewer
has been replaced by the more powerful nsINavHistoryResultObserver
.
Tree view
Create a built-in tree view by setting the type
attribute to "places" on a tree element. The treechildren
element should be empty:
<tree type="places"> <treecols> <treecol id="title" flex="1" primary="true" /> <treecol id="url" flex="1" /> </treecols> <treechildren /> </tree>
The tree view is implemented in browser/components/places/content/tree.xml
. See the tree reference and Trees tutorial for general information about trees.
If you would like your tree view to be styled in the same manner that Firefox styles its uses of the view, you should include the following stylesheet. Note that this stylesheet is different from the one given above, which must be included if you use a built-in Places view. The following stylesheet is optional and only applies styles and icons to the built-in tree view:
<?xml-stylesheet href="chrome://browser/skin/places/places.css" ?>
For examples of instantiating a tree view from JavaScript rather than XUL, see Programmatic generation of the built-in tree view.
Column binding
The built-in tree view
makes it easy to hook up specific columns of your tree to specific properties of the result. It does so by recognizing certain magic values of the id
attribute on your treecol
elements. A property in the result is bound to a column via that column's id
attribute. For example, by setting a column's id
to "title", you tell the view to display the title of the nsINavHistoryResultNode
of each row in that column.
The following table shows the mappings between these magic column id
values and their corresponding nsINavHistoryResultNode
properties:
treecol id or anonid |
Corresponding nsINavHistoryResultNode property |
title | title |
url | uri |
date | time |
visitCount | accessCount |
keyword | * |
description | * |
dateAdded | dateAdded |
lastModified | lastModified |
tags | tags |
** | icon |
*keyword and description are looked up in the Places database using the nsINavHistoryResultNode
property itemId
.
**The title column (and only the title column) automatically receives the favicon referenced by the nsINavHistoryResultNode
property icon.
You may specify as few or as many of these magic columns as you want, and your tree may of course contain other columns as well. In lieu of setting an id
on a treecol
, you may specify an anonid
attribute. This is useful when you need the id
for another purpose or when a treecol
is contained in anonymous content, as in XBL. If both an id
attribute and anonid
attribute are specified, the anonid
is used.
The built-in tree view is provided as a general-purpose convenience. If you need to display additional data or otherwise require more control over your view, you may need to write your own. See Creating custom views below.
Using the tree view
So you've got a built-in tree view. How do you ask it about the data it displays?
First, see nsIPlacesView
. Like all Places views, the built-in tree view implements this interface, which provides broad methods such as getting the view's nsINavHistoryResult
instance and examining the view's selection.
Second, know this: when it comes to trees, "view" is an overloaded word. This document describes Places views. Places views are just XUL elements; the built-in Places tree view is just a tree element whose type
attribute is equal to "places", as described above. But recall that all trees—Places view or not—display their data using another kind of view, nsITreeView
. The built-in Places tree view therefore has a view of its own, the built-in Places tree view's view. This view is an object that implements three interfaces: from most specific to most general, nsINavHistoryResultTreeViewer
, nsINavHistoryResultObserver
, and nsITreeView
. nsINavHistoryResultTreeViewer
maps between row indices and the nsINavHistoryResultNode
objects contained in the rows. nsINavHistoryResultObserver
notifies observing clients about updates when the underlying data changes. It's not so useful for our purposes here. nsITreeView
provides a high-level interface for trees in general.
Finally, the built-in tree view implements several convenience methods and properties of its own.
You therefore have four points of interaction with the built-in Places tree view:
- The convenience methods and properties implemented directly on the view itself,
- the
nsIPlacesView
interface of the view itself, - the
nsINavHistoryResultTreeViewer
interface of the view's view, and - the
nsITreeView
interface of the view's view.
The methods and properties implemented directly on the view are very specific, while the interface provided by nsITreeView
is very general. Tasks sometimes require you to use more than one of these points of interaction, going from one layer of generality to another.
To put it in terms of JavaScript, say you have a variable named treeView
that references your Places tree view:
var treeView = document.getElementById("myPlacesTreeView");
Points 1 and 2 apply to this variable.
The tree view's view is the object at treeView.view
. Points 3 and 4 apply to this object.
var treeViewView = treeView.view;
Convenience methods
Because the built-in tree view is both widely used and complex, several convenience methods are implemented directly on it to make common tasks easier.
place
attribute and property.applyFilter()
Loads a new empty query with particular search terms and folders.
void applyFilter( string filterString, array folderRestrict );
Parameters
-
filterString
-
The new query's
searchTerms
property will be set to this string. -
folderRestrict
-
The
setFolders
function of the new query will be called on this array of folder IDs. Optional.
load()
Sets the queries that the view displays. This method may be used as an alternative to setting the tree's place
property as described above.
void load( array queries, nsINavHistoryQueryOptions options );
Parameters
-
queries
-
An array of
nsINavHistoryQuery
objects. -
options
-
An
nsINavHistoryQueryOptions
object.
selectItems()
Selects the first node in the tree that matches each given item ID. It will open any parent nodes that it needs to in order to show the selected items.
void selectItems( array aIDs, array aOpenContainers );
Parameters
-
aIDs
- An array of item IDs.
-
aOpenContainers
- If true or undefined, folders that are closed are also searched. They are not searched otherwise. Optional.
selectNode()
Causes a particular node to be selected in the tree, resulting in all containers above the node in the hierarchy to be opened so that the node is visible.
void selectNode( nsINavHistoryNode node );
Parameters
-
node
-
The
nsINavHistoryResultNode
to select.
selectPlaceURI()
Causes a particular node represented by the specified placeURI to be selected in the tree. All containers above the node in the hierarchy will be opened so that the node is visible.
void selectPlaceURI( string placeURI );
Parameters
-
placeURI
-
The URI (as a string) of the
nsINavHistoryResultNode
to select.
Convenience properties
In addition to the methods above, some properties of convenience are implemented directly on the built-in tree view.
place
attribute and property.Property | Type | Description |
flatList |
boolean |
If true the view does not recurse into containers. The action to take when a container is toggled can be set via the onOpenFlatContainer property. At least one of flatList and showRoot must be false. |
onOpenFlatContainer |
string |
The body of function that will be called when a container is toggled. Only applies if property flatList is true. The function will be passed one nsINavHistoryResultNode argument named aContainer . You can QueryInterface aContainer to an nsINavHistoryContainerResultNode . |
showRoot |
boolean |
If true the root nsINavHistoryContainerResultNode is shown as the first row in the tree. At least one of showRoot and flatList must be false. |
Example uses
To get the nsINavHistoryResultNode
at a specific row:
var treeView = document.getElementById("myPlacesTreeView"); var rowIndex = 0; var historyResultNode = treeView.view.nodeForTreeIndex(rowIndex);
To get the row index of a specific nsINavHistoryResultNode
:
var treeView = document.getElementById("myPlacesTreeView");
var rowIndex = treeView.view.treeIndexForNode(historyResultNode);
To select a row in the tree whose node has a specific URI:
var treeView = document.getElementById("myPlacesTreeView"); treeView.selectPlaceURI("some place URI");
To select a row in the tree that contains a specific nsINavHistoryResultNode
:
var treeView = document.getElementById("myPlacesTreeView"); treeView.selectNode(someHistoryResultNode);
PlacesTreeView
The built-in tree view is backed by an instance of PlacesTreeView
, a prototype defined in browser/components/places/content/treeView.js
. PlacesTreeView
performs double duty for the built-in tree view: it implements both nsITreeView
and much of the functionality required of a Places view. The latter functionality is specified specifically by interface nsINavHistoryResultTreeViewer
, which inherits from the more general nsINavHistoryResultObserver
.
Because of PlacesTreeView
's double duty, you can use it to bridge a query result and a tree element:
var result = historyService.executeQuery(query, opts); // your Places query result var tree = document.getElementById("mytree"); // your tree element var showRootNodeInTree = true; var view = new PlacesTreeView(showRootNodeInTree); // Here's the bridge! result.addObserver(view.QueryInterface(Components.interfaces.nsINavHistoryResultObserver)); tree.view = view.QueryInterface(Components.interfaces.nsITreeView);
In fact this is how the built-in tree view works. It runs code similar to the above when you set its place
property or call its load
method.
While you are free to set up your tree view in this manner, it's not recommended if you are using the built-in view. Doing so circumvents updating the view's place
attribute, causing it to fall out of sync with the view's query result. Use the view's load
method or place
property instead and let it do the work for you. If, on the other hand, you are writing a custom tree view, you will need to write similar code at some point.
Menu view
You can display Places information in a popup menu.
In Firefox 3.6 and earlier
(Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)Create a built-in menu view by setting the type
attribute to "places" on an empty menupopup
element (which would be a child of some parent menu
element):
<menu> <menupopup type="places" /> </menu>
The place
attribute or property should be set on the menupopup
as well.
In Firefox 4 and later
(Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)You can add Places information to a popup like this:
<menu id="bookmarksMenu"> <menupopup placespopup="true"> onpopupshowing="if (!document.getElementById('bookmarksMenu')._placesView) new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');" </menupopup> </menu>
The menu view is implemented in browser/components/places/content/menu.xml
. See the menupopup and menu references and Popup Menus tutorial for general information about menus.
Toolbar view
Create a built-in toolbar view by setting the type
attribute to "places" on an empty hbox
element (which would be a child of some parent toolbaritem
element, itself the child of a toolbar
element):
<toolbar> <toolbaritem> <hbox type="places" /> </toolbaritem> </toolbar>
The place
attribute or property should be set on the hbox
as well.
The toolbar view is implemented in browser/components/places/content/toolbar.xml
. See the toolbaritem and toolbar references and Toolbars tutorial for general information about toolbars.
Using a view
So you've got a Places view. How do you interact with it?
Because it is fairly complex, the built-in tree view provides uniquely tailored interfaces to make it easier to use. If you write your own complex view, you might do something similar.
But all Places views should provide a minimal interface so that you and controllers have a consistent, general way to interact with them. For this reason views implement interface nsIPlacesView
. It allows you to do things like getting the nsINavHistoryResult
instance that a view displays and examining its selected nodes. In fact the special place
property described above is a property of this interface.
Creating custom views
If you need greater flexibility than a built-in view provides, you can create a custom view. Good reasons for needing a custom view might include (but are in no way limited to):
- Displaying custom columns alongside those provided by the built-in tree view.
- Changing the way a built-in view displays dates or other data.
- Inserting information into the display that is not determined by the underlying data.
- Displaying Places information in an element not covered by one of the built-in views.
Potentially bad reasons for creating a custom view might include (but are not limited to):
- Changing only the superficial appearance of a built-in view rather than the content it displays. Try CSS here.
- Hiding columns of the built-in tree view. Just leave out the
treecol
elements you want to hide. - Managing how the view responds to clicks, commands, and other user interaction. Don't break the separation of concerns here. Views are for displaying data, controllers for logic. You can hook up a built-in view to the standard Places commands and context menu, or you can create a custom controller to handle custom commands. See View Controller for more information.
Regardless of the kind of view you are writing, it should implement interface nsIPlacesView
. (nsIPlacesView
is currently not a true interface; the built-in views simply implement its methods and attributes directly.) As described above in Using a view, this interface provides controllers and other callers a consistent, general way to interact with views.
Your view should also either implement or own an instance of nsINavHistoryResultObserver
. To hook up your view to a nsINavHistoryResult
object, you call addObserver()
on the result, passing the viewer
object which is an instance of nsINavHistoryResultObserver
. If your view implements nsINavHistoryResultObserver
, then you can pass your viewer
object. If your view contains an internal instance of nsINavHistoryResultObserver
, you would pass it instead. (The three built-in views take this approach.) The results object then communicates to your view via this instance, notifying it about changes in the underlying data so it can update its display.
var bmServ = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]. getService(Components.interfaces.nsINavBookmarksService); var histServ = Components.classes["@mozilla.org/browser/nav-history-service;1"]. getService(Components.interfaces.nsINavHistoryService); var opts = histServ.getNewQueryOptions(); var query = histServ.getNewQuery(); query.setFolders([bmServ.placesRoot], 1); var result = histServ.executeQuery(query, opts); // Here myView is your view, and myView.viewer is some object implementing nsINavHistoryResultObserver. result.addObserver(myView.viewer);
More practically, a variation of the code above might be internal to a method on your view. A caller would pass in a result or a query, and you would execute the query and add your view as an observing client using addObserver()
. (That's how the load method of the built-in tree view works.)
When you let your view observe the result, the result will in turn set the result
property on the given nsINavHistoryResultObserver
. You should not set the result
property explicitly. To disconnect a viewer from its result, call removeObserver(view)
on your result.
Be careful about reference cycles. The viewer and the result both hold owning references to each other. For these objects to be freed during JavaScript garbage collection, you must clear this cycle by calling result.removeObserver(view)
. (For example, the built-in tree view does this automatically. When the tree is destroyed or a different nsITreeView
is associated with the tree, the tree will set the tree
property of the old nsITreeView
to null. Because the object implementing nsITreeView
also implements nsINavHistoryResultObserver
, the viewer then disconnects itself from the result by calling removeObserver()
.)
nsINavHistoryResultObserver?
Creating custom tree views
Custom Places tree views can be a lot of work. Take a look at Using the tree view above to get an idea of what's required. Custom nsITreeView
s in general are a lot of work, and if you are not familiar with creating custom nsITreeView
s, see the Custom Tree Views and Tree View Details pages in the XUL tutorial. All of that information is relevant here.
Fortunately you can commandeer parts of the built-in tree view to suit your purposes. The JavaScript prototype backing the built-in view, PlacesTreeView
, is especially useful since it implements all three of nsINavHistoryResultTreeViewer
, nsINavHistoryResultObserver
, and nsITreeView
. You can therefore implement your own custom functionality on top of PlacesTreeView
while relying on it for non-custom functionality and much of the tedious work. One strategy is to create your custom object(s) implementing each of these interfaces, support your custom behavior with them, but pass off everything else to a PlacesTreeView
instance that you would own. Perhaps an easier strategy is to modify an existing PlacesTreeView
instance, which is what the following example does.
The following JavaScript creates a new PlacesTreeView
instance but overrides two of its nsITreeView
methods to display columns not present in the built-in tree view:
var view = new PlacesTreeView(); view._getCellText = view.getCellText; view.getCellText = function (aRowIndex, aCol) { // Handle our special columns. As with PlacesTreeView, we'll recognize // them by their id's or anonid's. switch (aCol.id || aCol.element.getAttribute("anonid")) { // URI for all nodes (like folders), not just URI nodes (like bookmarks) case "fullURI": return this.nodeForTreeIndex(aRowIndex).uri; break; // Index of node in parent container case "indexInParent": return this.nodeForTreeIndex(aRowIndex).bookmarkIndex; break; // Is the row even or odd? case "parity": return (aRowIndex % 2 === 0 ? "even" : "odd"); break; } // Otherwise, pass off to the original getCellText method. return this._getCellText(aRowIndex, aCol); }; view._cycleHeader = view.cycleHeader; view.cycleHeader = function (aCol) { switch (aCol.id || aCol.element.getAttribute("anonid")) { case "fullURI": case "indexInParent": case "parity": // You might resort by column here. break; default: this._cycleHeader(aCol); break; } }; // Execute a query and gets its result. var bmServ = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]. getService(Components.interfaces.nsINavBookmarksService); var histServ = Components.classes["@mozilla.org/browser/nav-history-service;1"]. getService(Components.interfaces.nsINavHistoryService); var opts = histServ.getNewQueryOptions(); var query = histServ.getNewQuery(); query.setFolders([bmServ.placesRoot], 1); var result = histServ.executeQuery(query, opts); // Hook up the result's viewer and the tree's nsITreeView to our custom view. var treeView = document.getElementById("myTreeView"); result.addObserver(view); treeView.view = view;
The following XUL defines the myTreeView
element referenced in the JavaScript:
<tree id="myTreeView" flex="1"> <treecols> <treecol id="title" label="title" flex="1" primary="true" /> <splitter class="tree-splitter" /> <treecol anonid="fullURI" label="fullURI" flex="1" /> <splitter class="tree-splitter" /> <treecol id="indexInParent" label="indexInParent" /> <splitter class="tree-splitter" /> <treecol id="parity" label="parity" /> </treecols> <treechildren /> </tree>
The full code listing is available in the Files section of the page below.
Note that the tree element above does not have the special type
attribute common to the built-in views. It's just a regular tree element that you've hooked up to a Places query, with none of the convenience properties or methods of the built-in Places tree view. If your custom view is used only once in your application, code similar to the above will likely be sufficient; it's enough to create a single PlacesTreeView
object, modify it, and attach it to a regular tree. If your view is widely used, however, you may want to take a cue from the built-in tree view and create your own JavaScript prototype similar to PlacesTreeView
and your own XBL tree binding so that much of the work of creating views and viewers and hooking them up to results is abstracted away.
The attribute nsINavHistoryResultNode.viewIndex
is provided explicitly for the use of the view. This value is initialized to -1
when each node is created. You can use this value to keep track of visible nodes. PlacesTreeView
uses this attribute to store the row index that the node is on.