New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

cherrytree

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cherrytree

Cherrytree - a flexible hierarchical client side router

  • 2.0.0-alpha.9
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
5
decreased by-77.27%
Maintainers
1
Weekly downloads
 
Created
Source

Cherrytree

build status

Cherrytree is a flexible hierarchical client side router. Cherrytree translates each URL change to a transition object and applies your middleware functions that translate the transition data into the desired state of your application.

Installation

You can get cherrytree from npm - it supports both AMD and CJS.

$ npm install --save cherrytree@latest

In a CJS environment, simply require it as usual, the dependencies will be loaded from npm

require('cherrytree')

In an AMD environment, require the standalone UMD build - this version has all of the dependencies bundled

require('cherrytree/standalone')

Size

The size excluding all deps is ~10.23 kB gzipped and the standalone build with all deps is ~15.27 kB gzipped.

Usage

var cherrytree = require('cherrytree')

// create the router
var router = cherrytree()

// provide your route map
router.map(function (route) {
  route('application', {path: '/'}, function () {
    route('messages')
    route('status', {path: ':user/status/:id'})
    route('profile', {path: ':user'}, function () {
      route('profile.index')
      route('profile.lists')
      route('profile.edit')
    })
  })
})

// install any number of middleware
// middleware can be asynchronous
router.use(function (transition) {
  // e.g. use require.js to partially load your app
  return Promise.all(transition.routes.map(function (route) {
    return new Promise(function (resolve) {
      require(['./views/' + route.name], function (ViewClass) {
        route.ViewClass = ViewClass
        resolve()
      })
    })
  }))
})

// middleware can also be synchronous
router.use(function (transition) {
  transition.routes.forEach(function (route, i) {
    route.view = new route.ViewClass({
      params: transition.params,
      query: transition.query
    })
    var parent = transition.routes[i-1]
    var containerEl = parent ? parent.view.el.querySelector('.outlet') : document.body
    containerEl.appendChild(view.render().el)
  })
})

// transition itself is a promise
// use .then to know when transition has completed
// use .catch to know when transition has failed
router.use(function (transition) {
  transition.catch(function (err) {
    // transition can fail if it is cancelled or redirected
    // ignore those errors if you want to only listen to real errors
    // such as when one of the middleware fails, e.g. to fetch data
    if (err.type !== 'TransitionCancelled' && err.type !== 'TransitionRedirected') {
      dispatchError(err.message)
    }
  })
})

// start listening to URL changes
router.listen()

Guide

Read the brief guide.

Examples

You can clone this repo if you want to run the examples locally. Currently the examples are

  • hello-world - a single file example of how to get started
  • cherry-pick - a mini GitHub clone written in React.js
  • vanilla-blog - a simple static demo of blog like app that uses no framework
  • server-side-react - a simple server side express app using cherrytree for routing and react for rendering

Features

  • generate links in your application in a systematic way, e.g. router.generate('commit', {sha: '1e2760'})
  • use pushState with automatic hashchange fallback - all urls in your app are generated the right way depending on which mode you're in
  • link clicks on the page are intercepted automatically when using pushState
  • partially load your app during transitions
  • dynamic segments, optional params and query params
  • transition is a first class citizen - abort, pause, resume, retry. E.g. pause the transition to display "There are unsaved changes" message if the user clicked some link on the page or used browser's back/forward buttons
  • navigate around the app programatically, e.g. router.transitionTo('commits')
  • rename URL segments (e.g. /account -> /profile) without having to change route names or manuall update any links
  • not coupled to any framework

How does it compare to other routers?

  • Backbone router is nice and simple and can be enough. In fact cherrytree uses some bits from Backbone router under the hood. Cherrytree adds nested routing, support for asynchronous transitions, more flexible dynamic params, url generation, automatic click handling.
  • Ember router / router.js is the inspiration for cherrytree. It's where cherrytree inherits the idea of declaring hierarchical nested route maps. The scope of cherrytree is slightly different than that of router.js, for example cherrytree doesn't have the concept of handler objects or model hooks. On the other hand, unlike router.js - cherrytree handles browser url changes and intercepts link clicks with pushState out of the box. The handler concept and model hooks can be implemented based on the specific application needs using the middleware mechanism. Overall, cherrytree is less prescriptive, more flexible and easier to use out of the box.
  • react-router is also inspired by router.js. React-router is trying to solve a lot of routing related aspects out of the box in the most React idiomatic way whereas with cherrytree you'll have to write all of the glue code for integrating into React yourself. However, what you get instead is a smaller, simpler and hopefully more flexible library which should be more adaptable to your specific needs. This also means that you can use a react-router like a approach with other React inspired libraries such as mercury, riot or om.

Docs

var router = cherrytree(options)

  • options.log - a function that is called with logging info, default is noop. Pass in true or a custom logging function.
  • options.pushState - default is false, which means using hashchange events. Set to true to use pushState.
  • options.root - default is /. Use in combination with pushState: true if your application is not being served from the root url /.
  • options.interceptLinks - default is true. When pushState is used - intercepts all link clicks when appropriate, prevents the default behaviour and instead uses pushState to update the URL and handle the transition via the router. Read more on intercepting links below.

router.map(fn)

Configure the router with a route map. E.g.

router.map(function (route) {
  route('app', {path: '/'}, function () {
    route('index')
    route('about')
    route('post', {path: ':postId'}, function () {
      route('show')
      route('edit')
    })
  })
})
Nested paths

Nested paths are concatenated unless they start with a '/'. For example

router.map(function (route) {
  route('foo', {path: '/foo'}, function () {
    route('bar', {path: '/bar'}, function () {
      route('baz', {path: '/baz'})
    });
  })
})

The above map results in 1 URL /baz mapping to ['foo', 'bar', 'baz'] routes.

router.map(function (route) {
  route('foo', {path: '/foo'}, function () {
    route('bar', {path: 'bar'}, function () {
      route('baz', {path: 'baz'})
    });
  })
})

The above map results in 1 URL /foo/bar/baz mapping to ['foo', 'bar', 'baz'] routes.

Dynamic paths

Paths can contain dynamic segments as described in the docs of path-to-regexp. For example:

route('foo', {path: '/hello/:myParam'}) // single named param, matches /hello/1
route('foo', {path: '/hello/:myParam/:myOtherParam'}) // two named params, matches /hello/1/2
route('foo', {path: '/hello/:myParam?'}) // single optional named param, matches /hello and /hello/1
route('foo', {path: '/hello/:splat*'}) // match 0 or more segments, matches /hello and /hello/1 and /hello/1/2/3
route('foo', {path: '/hello/:splat+'}) // match 1 or more segments, matches /hello/1 and /hello/1/2/3

router.use(fn)

Add a transition middleware. Every time a transition takes place this middleware will be called with a transition as the argument. You can call use multiple times to add more middlewares. The middleware function can return a promise and the next middleware will not be called until the promise of the previous middleware is resolved. The result of the promise is passed in as a second argument to the next middleware. E.g.

router.use(function (transition) {
  return Promise.all(transition.routes.map(function (route) {
    return route.options.handler.fetchData()
  }))
})

router.use(function (transition, datas) {
  transition.routes.forEach(function (route, i) {
    route.options.handler.activate(datas[i])
  })
})
transition

The transition object is itself a promise. It also contains the following attributes

  • id
  • routes
  • path
  • pathname
  • params
  • query
  • prev
    • routes
    • path
    • pathname
    • params
    • query

And the following methods

  • then
  • catch
  • cancel
  • retry
  • followRedirects
route

During every transition, you can inspect transition.routes and transition.prev.routes to see where the router is transitioning to. These are arrays that contain a list of route descriptors. Each route descriptor has the following attributes

  • name - e.g. 'message'
  • path - the path segment, e.g. 'message/:id'
  • paramNames - a list of dynamic param names, e.g ['id']
  • options - the options object that was passed to the route function in the map
  • ancestors - an array of route names that are parents of this route, e.g. ['application', 'profile']

router.listen()

After the router has been configured with a route map and middleware - start listening to URL changes and transition to the appropriate route based on the current URL.

router.transitionTo(name, params, query)

Transition to a route, e.g.

router.transitionTo('about')
router.transitionTo('posts.show', {postId: 1})
router.transitionTo('posts.show', {postId: 2}, {commentId: 2})

router.replaceWith(name, params, query)

Same as transitionTo, but doesn't add an entry in browser's history, instead replaces the current entry. Useful if you don't want this transition to be accessible via browser's Back button, e.g. if you're redirecting, or if you're navigating upon clicking tabs in the UI, etc.

router.generate(name, params, query)

Generate a URL for a route, e.g.

router.generate('about')
router.generate('posts.show', {postId: 1})
router.generate('posts.show', {postId: 2}, {commentId: 2})

It generates a URL with # if router is in hashChange mode and with no # if router is in pushState mode.

router.state

The state of the route is always available on the router.state object. It contains activeTransition, routes, path, pathname, params and query.

Errors

Transitions can fail, in which case the transition promise is rejected with the error object. This could happen, for example, if some middleware throws or returns a rejected promise.

There are also two special errors that can be thrown when a redirect happens or when transition is cancelled completely.

In case of redirect (someone initiating a router.transitionTo() while another transition was active) and error object will have a type attribute set to 'TransitionRedirected' and nextPath attribute set to the path of the new transition.

In case of cancelling (someone calling transition.cancel()) the error object will have a type attribute set to 'TransitionCancelled'.

If you have some error handling middleware - you most likely want to check for these two special errors, because they're normal to the functioning of the router, it's common to perform redirects.

HistoryLocation

Cherrytree can be configured to use differet implementations of libraries that manage browser's URL/history. By default, Cherrytree will use a very versatile implementation - cherrytree/lib/locations/history which supports pushState and hashChange based URL management with graceful fallback of pushState -> hashChange -> polling depending on browser's capabilities.

Configure HistoryLocation by passing options directly to the router.

  var cherrytree = require('cherrytree')
  var router = cherrytree({
    pushState: true
  })
  router.listen()

You can also pass the location in explicitly. This is how you could provide your own custom location implementation.

  var cherrytree = require('cherrytree')
  var HistoryLocation = require('cherrytree/lib/locations/history')
  var router = cherrytree()
  router.listen(new HistoryLocation({
    pushState: true
  }))

var location = new HistoryLocation(options)

Create an instance of history location. Note that only one instance of HistoryLocation should be created per page since it's managing the browser's URL.

Note these options can be passed in as router options, since HistoryLocation is the default location.

  • options.pushState - default is false, which means using hashchange events. Set to true to use pushState.
  • options.root - default is /. Use in combination with pushState: true if your application is not being served from the root url /.

MemoryLocation

MemoryLocation can be used if you don't want router to touch the address bar at all. Navigating around the application will only be possible programatically by calling router.transitionTo and similar methods.

e.g.

var cherrytree = require('cherrytree')
var MemoryLocation = require('cherrytree/lib/locations/memory')
var router = cherrytree()
routerlisten(new MemoryLocation())

The clicks are intercepted only if:

  • router is passed a interceptLinks: true (default)
  • the currently used location and browser supports pushState
  • clicked with the left mouse button with no cmd or shift key

The clicks that are never intercepted:

  • external links
  • javascript: links
  • links with a data-bypass attribute
  • links starting with #

Keywords

FAQs

Package last updated on 09 Aug 2015

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc