As you’ll see below, hashable.hash()
and other objects are
designed for chaining,
which simply means that most methods return the object instance. These
objects expose most of their parameters as getter/setter functions,
which serve a dual purpose: when called with no arguments they return
the value of a particular property (such as a hash’s
format); whereas if they get an argument they set the
corresponding property value internally. These conventions should be
familiar to users of jQuery or d3.
The hashable.hash()
function returns an object that manages state
data via the browser’s location.hash
. Here is a typical usage:
var hash = hashable.hash()
.change(function(e) {
// this gets called whenever the hash changes
})
.enable()
.check();
Register a single callback function to be called whenever the hash changes, and when initialized by hash.check. This can either be an inline function or a function reference:
hash.change(function(e) {
});
// or
function hashchange(e) {
}
hash.change(hashchange);
The callback function receives a single argument: an "event" object with the following properties:
url
: the URL as parsed, without the leading #
.data
: the state data, as set or parsed by the chosen format.previous
: the previous state data, or null
if there was none.diff
: an object listing state data properties that changed
between the previous
and current objects. This will be null
if
the data has not changed according to hashable.diff.Please note that the state data is stored in event.data
, not in
the event object itself. In other words, if your code looks like this:
hash.change(app.setState);
And your app.setState()
method expects a state object as its first
argument, then you will not get the expected results. Try this instead:
hash.change(function(e) {
if (e.data) {
app.setState(e.data);
} else {
// handle bad URLs here
}
});
The diff
property in the change callback’s event argument
tells you which properties of the state data have changed since the
last time the hash was read. The diff is a JavaScript object with one
property (or "key") per property that was added, removed or changed.
This object can be used to check whether specific values have changed
and avoid performing potential costly operations on ones that
haven’t. Here’s an example:
location.hash = "widgets/foo/small";
var hash = hashable.hash()
.format("widgets/{widget}/{size}")
.change(function(e) {
if (e.data) {
showWidget(e.data.widget);
setWidgetSize(e.data.size);
}
})
.enable()
.check();
Let’s assume that the showWidget()
function is costly in some
way—maybe it triggers an AJAX request, or triggers transitions
or animations—but that setWidgetSize()
can be called
repeatedly without any weird side effects (in other words, it’s
idempotent). In this
case, we could rewrite our change callback like so:
hash.change(function(e) {
if (e.data) {
if (e.diff.widget) showWidget(e.data.widget);
if (e.diff.size) setWidgetSize(e.data.size);
}
});
This way, the showWidget()
function only gets called when the value
of e.data.widget
has changed—and we don’t have to do
any complicated comparison of the previous and current values in the
change callback. Hooray!
The hash.check()
function is the suggested way to initialize your
state data. It first checks location.hash
to see if the page has
loaded with a URL to parse, and attempts to parse it if so.
Otherwise, it uses the default data to set
location.hash
according to its format.
Calling hash.data()
with an object sets its state data, and calling
it without any arguments returns the current state data object:
hash.state({widget: "foo"}); // set the state, returns the `hash` object
hash.state(); // returns: {widget: "foo"}
Please note that the setter does not update location.hash
automatically. You must call hash.write after
setting the data, or call hash.set.
The hash.default()
function gets or sets the manager’s
default state data. The setter accepts either an object literal or a
function:
hash.default({widget: "foo"});
hash.default(function() {
return {widget: "some dynamic value"};
});
If a default is provided, it will be used to by
hash.check to set location.hash
using the specified
format.
Enable the listening of hashchange
events.
Please note that hash managers are not enabled by default.
Disable the listening of hashchange
events. You may wish to do this
if you’re temporarily monkeying with location.hash
and
don’t want to deal with change callbacks, or if you’re
replacing one hash manager with another.
Get or set the format string or formatting function for the hash. If
the formatting function has a parse()
method (such as those
returned by hash.format and its brethren will), the
hash mangager will use that, too. The setter form accepts either a
function or a string, the latter of which is converted into a
formatting function with hashable.format:
// these are equivalent:
hash.format("widgets/{widget}");
hash.format(hashable.format("widgets/{widget}"));
// this is not, because the format supports query parameters:
var format = hashable.format("widgets/{widget}")
.query(true);
hash.format(format);
Remember that any old function will suffice, as long as it takes an object as its first argument and returns a string. For instance, if you wish to have your state data formatted and parsed as JSON:
hash.format(JSON.stringify);
hash.parse(JSON.parse);
The parse()
method complements hash.format,
allowing you to get or set the function used to parse URLs into state
data. You won’t need to use this method in conjunction with any
of hashable’s built-in formatters because they also provide a
parse()
method of their own (following a convention established by
d3), but if writing
your own URL format you may wish to define the formatting and parsing
functions separately.
// this is how you might implement a path format
// similar to hashable.format.path()
hash.format(function(data) {
return data.join("/");
});
hash.parse(function(url) {
return url.split("/");
});
Force the hash manager to read location.hash
and call the change
function if it differs from the most recent value.
hash.read();
Set the state data value either as an object or a string and update
location.hash
accordingly. If value is a string, location.hash
is set to that value and a change event will only be triggered if it
differs from the previous value.
var hash = hashable.hash()
.format("widgets/{widget}");
hash.set("widgets/foo");
// hash.data() should now be: {widget: "foo"}
hash.set({widget: "bar"});
// hash.data() should now be: {widget: "bar"}
If you’re looking to merge values into or modify the current state, check out hash.update.
Merge the keys of the object data into the current state data, and do nothing else. This is useful in the event that you wish to add or remove a single property value from the state without having to know the other values.
var hash = hashable.hash()
.data({widget: "bar"})
.update({size: "small"});
// hash.data() now returns: {widget: "bar", size: "small"}
Please note that automatic setting of location.hash
isn’t
done here so that you can update the state data multiple times
without having to manage intermediate state changes. When
you’re done, just call hash.write.
This function produces URLs from state data. If merge is true, the
provided state data is merged with the manager’s current state,
which allows you to specify only the values that you wish to change
in the URL. hash.url()
can be used to create links on your page
that modify the state:
var hash = hashable.hash()
.format("widgets/{widget}/{size}")
.data({widget: "foo", size: "small"});
document.querySelector("a.widget-bar")
.setAttribute("href", hash.url({widget: "bar", size: "medium"}));
document.querySelector("a.medium")
.setAttribute("href", hash.url({size: "medium"}, true));
For more link-related magic, check out hash.href.
Write the state data to location.hash
immediately. If silent is truthy, the
change callback will not be called.
This function and its children are intended for use with d3 selections.
hash.href()
adds a click
handler to the nodes in the selection
that sets their href
property to the return value of
hash.url. For instance:
var hash = hashable.hash()
.format("widgets/{widget}");
var link = d3.select("#links")
.selectAll("a.widget")
.data(["foo", "bar", "baz"].map(function(widget) {
return {widget: widget};
})
.enter()
.append("a")
.call(hash.href);
Similar to hash.href the hash.href.merge()
can be
used to set hrefs on links, but by merging each link’s bound
datum with the hash’s current state data:
var hash = hashable.hash()
.format("widgets/{widget}/{size}");
d3.selectAll("a.size")
.datum(function() {
// data-size="medium" -> {size: "medium"}
return this.dataset;
})
.call(hash.href.merge);
The purpose of this function is to parse #
-prefixed href values as
state data, which can then be bound back to DOM nodes for use with
hash.href and hash.href.merge. This
is useful for making HTML with stateful links dynamic, so that
whenever one is clicked its href is updated (before being read by the
browser) to reflect the current state:
var hash = hashable.hash()
.data({widget: "foo", size: "small"})
d3.select("body")
.append("a")
.attr("href", "#?size=small")
.datum(hash.href.parse)
.call(hash.href.merge);
Hashable comes bundled with a Leaflet plugin
that exposes a handler at L.hash
, which can be used like so:
var map = L.map("div", {...}),
hash = L.hash()
.addTo(map);
// or:
map.addLayer(hash);
L.hash()
objects are just hashable.hash()
objects with additional
methods that expose Leaflet’s ILayer
interface. If you want
to listen for change events (for instance, to capture query
string parameters), you can listen for hashchange
events on the map object:
map.on("hashchange", function(e) {
console.log("hash change:", e.data);
});
You can change the URL format of L.hash()
instances, but be sure to include the {z}
, {x}
and {y}
placeholders (zoom, longitude and latitude, respectively) so that the
map knows where to go.