API reference

Common Conventions

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.

# hashable.hash()

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();

# hash.change(callback)

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
  }
});
Data diffs

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!

# hash.check()

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.

# hash.data([data])

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.

# hash.default([default])

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.

# hash.enable()

Enable the listening of hashchange events.

Please note that hash managers are not enabled by default.

# hash.disable()

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.

# hash.format([format])

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);

# hash.parse([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("/");
});

# hash.read()

Force the hash manager to read location.hash and call the change function if it differs from the most recent value.

hash.read();

# hash.set(value)

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.

# hash.update([data])

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.

# hash.url(data[, merge])

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.

# hash.write(silent)

Write the state data to location.hash immediately. If silent is truthy, the change callback will not be called.

# hash.href()

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);

# hash.href.merge()

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);

# hash.href.parse()

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.format([format])

# format(data)

# format.match(url)

# format.parse(url)

# format.query([boolean])

# hashable.format.path()

# path(data)

# path.match(url)

# path.parse(url)

# hashable.format.map()

# map(data)

# map.match(url)

# map.parse(url)

# map.precision([precision])

# hashable.validFragment(url)

# Utility functions

# hashable.copy(obj[, keys])

# hashable.diff(a, b)

# hashable.empty(value)

# hashable.extend(a, b[, c[, ...]])

# hashable.functor(value)

# Plugins

# Leaflet

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.

Fork me on GitHub