cherrytree
Advanced tools
Comparing version 0.2.1 to 0.3.0
@@ -25,4 +25,5 @@ { | ||
"jquery": "*", | ||
"underscore": "*" | ||
"underscore": "*", | ||
"sinon": "~1.10.2" | ||
} | ||
} |
@@ -6,11 +6,6 @@ (function (define) { 'use strict'; | ||
function DSL(name, router) { | ||
this.parent = name; | ||
this.router = router; | ||
function DSL(parent) { | ||
this.parent = parent; | ||
this.matches = []; | ||
this.prepares = {}; | ||
// TODO - remove these; backwards compatibility | ||
this.state = this.addRoute; | ||
this.states = this.addRoutes; | ||
this.resolvers = {}; | ||
} | ||
@@ -33,12 +28,11 @@ | ||
// a method we'll call before entering this route | ||
if (options.prepare) { | ||
this.prepares[name] = options.prepare; | ||
if (options.resolver) { | ||
this.addResolver(name, options.resolver); | ||
} | ||
if (callback) { | ||
var dsl = new DSL(name, this.router); | ||
var dsl = new DSL(name); | ||
callback.call(dsl); | ||
this.push(options.path, name, dsl.generate(), options.queryParams); | ||
_.extend(this.prepares, dsl.prepares); | ||
_.extend(this.resolvers, dsl.resolvers); | ||
} else { | ||
@@ -49,12 +43,3 @@ this.push(options.path, name, null, options.queryParams); | ||
push: function (url, name, callback, queryParams) { | ||
var parts = name.split('.'); | ||
if (url === "" || url === "/" || parts[parts.length - 1] === "index") { this.explicitIndex = true; } | ||
this.matches.push([url, name, callback, queryParams]); | ||
}, | ||
route: function (name, options) { | ||
// Ember.assert("You must use `this.resource` to nest", typeof options !== 'function'); | ||
options = options || {}; | ||
@@ -70,4 +55,4 @@ | ||
if (options.prepare) { | ||
this.prepares[name] = options.prepare; | ||
if (options.resolver) { | ||
this.addResolver(name, options.resolver); | ||
} | ||
@@ -78,2 +63,13 @@ | ||
push: function (url, name, callback, queryParams) { | ||
var parts = name.split('.'); | ||
if (url === "" || url === "/" || parts[parts.length - 1] === "index") { this.explicitIndex = true; } | ||
this.matches.push([url, name, callback, queryParams]); | ||
}, | ||
addResolver: function (name, fn) { | ||
this.resolvers[name] = fn; | ||
}, | ||
generate: function () { | ||
@@ -90,21 +86,9 @@ var dslMatches = this.matches; | ||
match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); | ||
// var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); | ||
// if (dslMatch[3]) { | ||
// matchObj.withQueryParams.apply(matchObj, dslMatch[3]); | ||
// } | ||
} | ||
}; | ||
}, | ||
addRoute: function () { | ||
this.router.addRoute.apply(this.router, arguments); | ||
}, | ||
addRoutes: function () { | ||
this.router.addRoutes.apply(this.router, arguments); | ||
} | ||
}; | ||
DSL.map = function (router, callback) { | ||
var dsl = new DSL(null, router); | ||
DSL.map = function (callback) { | ||
var dsl = new DSL(); | ||
callback.call(dsl); | ||
@@ -111,0 +95,0 @@ return dsl; |
@@ -123,12 +123,2 @@ (function (define) { 'use strict'; | ||
// // DEPRECATED | ||
// // set route.options for backwards compatiblity, | ||
// // with params, and set queryParams in options as well. | ||
// // This all will go away, the params and queryParams | ||
// // should be grabbed via model arguments, etc. now that | ||
// // the route objects are ever living. | ||
// this.route.options = this.route.options || {}; | ||
// _.extend(this.route.options, params); | ||
// this.route.options.queryParams = params.queryParams || {}; | ||
// the fact that we're calling model on this route means | ||
@@ -210,7 +200,6 @@ // we'll need to reactive it. This is flagged here | ||
var seen = {}; | ||
var routeClasses = router.routeClasses; | ||
var createRoute = makeRouteCreator(router); | ||
// special case "loading" route | ||
if (!routeClasses["loading"]) { | ||
if (!router.routes["loading"]) { | ||
seen.loading = {}; | ||
@@ -217,0 +206,0 @@ } else { |
@@ -7,8 +7,6 @@ (function (define) { 'use strict'; | ||
return function routeCreator(router) { | ||
var routeClasses = router.routeClasses; | ||
var prepares = router.prepares; | ||
var preparesCalled = {}; | ||
var cache = {}; | ||
function createRoute(name) { | ||
var Route = routeClasses[name] || router.options.BaseRoute; | ||
var Route = cache[name] || router.options.BaseRoute; | ||
return new Route({ | ||
@@ -20,29 +18,52 @@ name: name, | ||
// TODO 2014-03-24 this async prepare loading might get | ||
// messed up if we call createRoute multiple times, because | ||
// we'll call prepares[routeName] multiple times and that | ||
// might break - we should guarantee to only call that once | ||
// and wait in case it's called multiple times. | ||
function resolveRoute(name, cb) { | ||
return getRouteResolver(name)(name, cb); | ||
} | ||
function getRouteResolver(name) { | ||
var routeName, i, l; | ||
var branches = router.getBranchNames(name); | ||
for (i = branches.length - 1, l = 0; i >= l; i--) { | ||
routeName = branches[i]; | ||
if (router.resolvers[routeName]) { | ||
return router.resolvers[routeName]; | ||
} | ||
} | ||
} | ||
function promise(value) { | ||
return new Promise(function (resolve) { | ||
resolve(value); | ||
}); | ||
} | ||
return function getRoute(name) { | ||
// if we don't have a prepare method for this route | ||
// or if it's already been called - proceed with creating | ||
// the route | ||
if (!prepares[name] || preparesCalled[name]) { | ||
return new Promise(function (resolve) { | ||
return resolve(createRoute(name)); | ||
// if we have resolved this previously, or are in the | ||
// process of resolving - wait on the promise and | ||
// then create the route | ||
if (cache[name] && cache[name].then) { | ||
return cache[name].then(function (name) { | ||
return createRoute(name); | ||
}); | ||
} else { | ||
return new Promise(function (resolve) { | ||
prepares[name](router, function () { | ||
// record that this prepare has been called - we only | ||
// do this once per lifetime of application as it's | ||
// mostly intended for loading extra code | ||
preparesCalled[name] = true; | ||
return resolve(createRoute(name)); | ||
}); | ||
}); | ||
} | ||
}; // wow.. what's happening here | ||
// if we have resolved previously | ||
// but it's not a promise, return the promise | ||
// for the route | ||
if (cache[name]) { | ||
return promise(createRoute(name)); | ||
} | ||
// use the route resolver to get the Route | ||
cache[name] = new Promise(function (resolve, reject) { | ||
resolveRoute(name, function (route) { | ||
cache[name] = route; | ||
resolve(createRoute(name)); | ||
}, reject); | ||
}); | ||
return cache[name]; | ||
}; | ||
}; | ||
}); | ||
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); |
{ | ||
"name": "cherrytree", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "Cherrytree is a hierarchical router for clientside web applications.", | ||
@@ -19,3 +19,3 @@ "main": "router", | ||
"dependencies": { | ||
"location-bar": "^2.0.0-beta.1" | ||
"location-bar": "^2.0.0" | ||
}, | ||
@@ -30,11 +30,11 @@ "keywords": [ | ||
"devDependencies": { | ||
"chai": "~1.8.0", | ||
"karma": "~0.10.9", | ||
"karma-chrome-launcher": "~0.1.0", | ||
"karma-firefox-launcher": "~0.1.0", | ||
"karma-chrome-launcher": "~0.1.0", | ||
"karma-mocha": "~0.1.0", | ||
"karma-requirejs": "~0.2.1", | ||
"chai": "~1.8.0", | ||
"mocha": "~1.17.0", | ||
"karma-sinon-chai": "~0.1.0", | ||
"karma-mocha": "~0.1.0" | ||
"mocha": "^1.20.1" | ||
} | ||
} |
@@ -39,3 +39,3 @@ # Cherrytree | ||
In Cherrytree, being in a certain state of your app means that several Routes are active. For example, if you're at `app.com/QubitProducts/cherrytree/commits` - the list of active routes would look like `['application', 'repository', 'commits', 'commits.index']`. You can define behaviour of each route by extending `cherrytree/route` and registering the routes by `router.addRoute`. | ||
In Cherrytree, being in a certain state of your app means that several Routes are active. For example, if you're at `app.com/QubitProducts/cherrytree/commits` - the list of active routes would look like `['application', 'repository', 'commits', 'commits.index']`. You can define behaviour of each route by extending `cherrytree/route` and registering the routes in the `router.routes` hash. | ||
@@ -91,5 +91,3 @@ Application route could render the outer shell of the application, e.g. the nav and container for child routes, it could also initialize some base models, e.g. user model. The repository route would load in the repository model from the server based on the URL params, the `commits.index` would load the specific commit model and render that out. | ||
var router = new Router({ | ||
location: new HistoryLocation({ | ||
pushState: false | ||
}) | ||
location: new HistoryLocation() | ||
}); | ||
@@ -107,3 +105,3 @@ | ||
// application route is always the root route | ||
router.addRoute("application", Route.extend({ | ||
router.routes["application"] = Route.extend({ | ||
activate: function () { | ||
@@ -113,5 +111,5 @@ this.view = $("<div><h1>My Blog</h1><div class='outlet'></div></div>"); | ||
} | ||
})); | ||
}); | ||
// let's load in the model | ||
router.addRoute("post", Route.extend({ | ||
router.routes["post"] = Route.extend({ | ||
model: function (params) { | ||
@@ -126,5 +124,5 @@ this.post = new Post({ | ||
} | ||
})); | ||
}); | ||
// and display it | ||
router.addRoute("post.show", Route.extend({ | ||
router.routes["post.show"] = Route.extend({ | ||
activate: function () { | ||
@@ -137,3 +135,3 @@ this.view = $("<p>" + this.get("post").get("content") + "</p>"); | ||
} | ||
})); | ||
}); | ||
@@ -157,6 +155,9 @@ // let's do this! | ||
* options.location - default is NoneLocation. Use HistoryLocation if you want router to hook into the URL (see example above) | ||
* options.logging - default is false. | ||
* options.BaseRoute - default is `cherrytree/route`. Change this to specify a different default route class that will be used for all routes that don't have a specific class configured. | ||
* onURLChanged - e.g. function (url) {} | ||
* onDidTransition: function (path) {} | ||
* options.logging - default is false | ||
* options.BaseRoute - default is `cherrytree/route`. Change this to specify a different default route class that will be used for all routes that don't have a specific class configured | ||
* options.map - specify the route map | ||
* options.resolver - specify a custom route resolver. Default resolver loads the routes from the router.routes by name `function (name, cb) { cb(router.routes[name]); }` | ||
* options.routes - a hash specifying your route classes. Key is the name of the route, value is the route class | ||
* options.onURLChanged - e.g. function (url) {} | ||
* options.onDidTransition: function (path) {} | ||
@@ -177,9 +178,10 @@ ### router.map(fn) | ||
### router.addRoute(name, obj) | ||
### router.routes hash | ||
Each route in your route map can have specific behaviour, such as loading data and specifying how to render the views. Each resource in the map can have an associated route class, the name of the resource what you use to attach the route class to it, e.g. `post`. Each route in the map has a name that can be created by combining the name of the resource and the route, e.g. `post.show`. Top level route names don't include a prefix, e.g. `about`. There are a couple of special routes that are always available - `application` and `loading`. | ||
This is where you register all your custom route classes. Each route in your route map can have specific behaviour, such as loading data and specifying how to render the views. Each resource in the map can have an associated route class, the name of the resource is what you use to attach the route class to it, e.g. `post`. Each route in the map has a name that can be created by combining the name of the resource and the route, e.g. `post.show`. Top level route names don't include a prefix, e.g. `about`. There are a couple of special routes that are always available - `application` and `loading`. | ||
```js | ||
var Route = require('cherrytree/route'); | ||
router.addRoute('post.show', Route.extend({ | ||
router.routes['post.show'] = Route.extend({ | ||
model: function () { | ||
@@ -191,16 +193,5 @@ return $.getJSON('/url'); | ||
} | ||
})); | ||
}); | ||
``` | ||
### router.addRoutes(obj) | ||
Register multiple route classes at once, example: | ||
```js | ||
router.addRoutes({ | ||
'posts.show': require('./routes/posts_show_route'), | ||
'posts.edit': require('./routes/posts_edit_route') | ||
}) | ||
``` | ||
### router.startRouting() | ||
@@ -225,3 +216,16 @@ | ||
### router.generate(name, ...params) | ||
Generate a URL for a route, e.g. | ||
```js | ||
router.generate('about'); | ||
router.generate('posts.show', 1); | ||
router.generate('posts.show', 2, {queryParams: {commentId: 2}}); | ||
router.generate('posts.show', 2, {queryParams: {commentId: null}}); | ||
``` | ||
It generates a URL with # if router is in hashChange mode and with no # if router is in pushState mode. | ||
### router.activeRoutes() | ||
@@ -244,2 +248,4 @@ | ||
Within a route you can access the router via `this.router`. | ||
## Route Hooks | ||
@@ -374,8 +380,36 @@ | ||
* options.pushState - default is true. Whether to use pushState, set false for hashState. | ||
* options.root - default is `/`. Use this if your application is not being served from the root url /. | ||
* 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. Appropriate link clicks are links that are clicked with the left mouse button with no cmd or shift key. External links, `javascript:` links, links with a `data-bypass` attribute and links starting with `#` are not intercepted. | ||
## Custom resolvers | ||
By default, cherrytree always look at your `router.routes` hash to find all the route classes. However, you can override the resolver to load routes from anywhere else. That is especially useful if you want to be loading your routes asynchronously. It's possible to override the global resolver or specify a per route/resource resolver in the route map. For example, if we wanted to load each route asynchronously using AMD style require, we could do this: | ||
```js | ||
var router = new Router({ | ||
resolver: function (name, cb) { | ||
require(["app/routes/" + name + ".route.js"], function (Route) { | ||
cb(Route); | ||
}); | ||
}, | ||
map: function () { | ||
this.resource("branches", function () { | ||
this.route("stale"); | ||
this.route("merged"); | ||
}); | ||
} | ||
}); | ||
``` | ||
Now the right routes will only be resolved if needed by a given transition, e.g. `router.transitionTo('branches.merged')` would load `app/routes/application.route.js`, `app/routes/branches.route.js` and `app/routes/branches.merged.route.js`, but would not load the `app/routes/branches.stale.route.js`. This allows splitting your application into multiple bundles. | ||
# Changelog | ||
## 0.3.0 | ||
* Global and per route/resource resolvers allow loading in each route asyncronously on demand. Read more about it in the "Custom resolvers" section. `addRoute` and `addRoutes` methods have been removed. The default global resolver expects the routes to be added to the `router.routes` hash. | ||
## 0.2.1 | ||
@@ -382,0 +416,0 @@ |
(function (define) { 'use strict'; | ||
define(function (require) { | ||
var _ = require("./lib/util"); | ||
@@ -11,23 +11,4 @@ var Router = require("./vendor/router"); | ||
var CherrytreeRoute = function (options) { | ||
this.options = _.extend({ | ||
location: noneLocation(), | ||
logging: false, | ||
onDidTransition: null, | ||
onURLChanged: null, | ||
BaseRoute: BaseRoute | ||
}, options); | ||
this.routeClasses = {}; | ||
this.prepares = {}; | ||
if (this.options.logging) { | ||
this.log = function () { | ||
console && console.log.apply(console, arguments); | ||
}; | ||
} | ||
// the underlying router.js ember microlib | ||
this.router = new Router(); | ||
this.router.log = this.log; | ||
var CherrytreeRoute = function () { | ||
this.initialize.apply(this, arguments); | ||
}; | ||
@@ -37,6 +18,46 @@ | ||
initialize: function (options) { | ||
var router = this; | ||
// the underlying router.js ember microlib | ||
this.router = new Router(); | ||
this.router.log = this.log; | ||
this.resolvers = {}; | ||
this.routes = {}; | ||
this.options = _.extend({ | ||
location: noneLocation(), | ||
logging: false, | ||
onDidTransition: null, | ||
onURLChanged: null, | ||
BaseRoute: BaseRoute, | ||
resolver: function (name, cb) { | ||
cb(router.routes[name]); | ||
}, | ||
map: null | ||
}, options); | ||
if (this.options.routes) { | ||
this.routes = this.options.routes; | ||
} | ||
if (this.options.resolver) { | ||
this.resolvers["application"] = this.options.resolver; | ||
} | ||
if (this.options.logging) { | ||
this.log = function () { | ||
console && console.log.apply(console, arguments); | ||
}; | ||
} | ||
if (this.options.map) { | ||
this.map(this.options.map); | ||
} | ||
}, | ||
map: function (callback) { | ||
var router = this.router; | ||
var dsl = RouterDSL.map(this, function () { | ||
var dsl = RouterDSL.map(function () { | ||
this.resource("application", { path: "/" }, function () { | ||
@@ -48,18 +69,7 @@ callback.call(this); | ||
router.map(dsl.generate()); | ||
_.extend(this.resolvers, dsl.resolvers); | ||
this.prepares = dsl.prepares; | ||
return this; | ||
}, | ||
addRoute: function (name, route) { | ||
this.routeClasses[name] = route; | ||
}, | ||
addRoutes: function (map) { | ||
_.each(map, function (route, name) { | ||
this.addRoute(name, route); | ||
}, this); | ||
}, | ||
startRouting: function () { | ||
@@ -107,2 +117,13 @@ var self = this; | ||
getBranchNames: function (name) { | ||
if (name === "application") { | ||
return ["application"]; | ||
} else if (name === "loading") { | ||
return ["application", "loading"]; | ||
} else { | ||
var names = this.router.recognizer.names[name]; | ||
return _.pluck(names.handlers, "handler"); | ||
} | ||
}, | ||
activeRoutes: function (name) { | ||
@@ -178,3 +199,3 @@ var activeRoutes = _.pluck(_.pluck(this.router.currentHandlerInfos, "handler"), "route"); | ||
/** | ||
* | ||
* | ||
*/ | ||
@@ -181,0 +202,0 @@ |
@@ -62,3 +62,3 @@ define(function (require) { | ||
router.addRoute("postsAdmin.create", BaseRoute.extend({ | ||
router.routes["postsAdmin.create"] = BaseRoute.extend({ | ||
update: function () { | ||
@@ -71,5 +71,5 @@ sequence.push("update " + this.name); | ||
} | ||
})); | ||
}); | ||
router.addRoute("settings.photo", BaseRoute.extend({ | ||
router.routes["settings.photo"] = BaseRoute.extend({ | ||
update: function () { | ||
@@ -82,3 +82,3 @@ sequence.push("update " + this.name); | ||
} | ||
})); | ||
}); | ||
@@ -93,3 +93,3 @@ router.startRouting().then(done, done); | ||
it("should handle rapid retransitioning", function (done) { | ||
router.addRoute("posts.show", BaseRoute.extend({ | ||
router.routes["posts.show"] = BaseRoute.extend({ | ||
initialize: function () { | ||
@@ -111,3 +111,3 @@ sequence.push("initialize " + this.name); | ||
} | ||
})); | ||
}); | ||
router.transitionTo("about").then(function () { | ||
@@ -260,3 +260,3 @@ sequence = []; | ||
it("should still deactivate the previous state with the same name", function (done) { | ||
router.addRoute("posts.show", BaseRoute.extend({ | ||
router.routes["posts.show"] = BaseRoute.extend({ | ||
initialize: function () { | ||
@@ -278,3 +278,3 @@ sequence.push("initialize " + this.name); | ||
} | ||
})); | ||
}); | ||
router.transitionTo("about").then(function () { | ||
@@ -281,0 +281,0 @@ sequence = []; |
@@ -5,3 +5,4 @@ require.config({ | ||
"jquery": "bower_components/jquery/dist/jquery", | ||
"location-bar": "bower_components/location-bar/location-bar" | ||
"location-bar": "bower_components/location-bar/location-bar", | ||
"sinon": "bower_components/sinon/lib/sinon", | ||
}, | ||
@@ -8,0 +9,0 @@ packages: [{ |
@@ -52,3 +52,3 @@ // map(function(match) { | ||
// first of all, we want an application state | ||
router.addRoute("application", State.extend({ | ||
router.routes["application"] = State.extend({ | ||
// this is a cherrytree hook for "performing" | ||
@@ -71,18 +71,18 @@ // actions upon entering this state | ||
} | ||
})); | ||
}); | ||
// then we'll create an application.index state that | ||
// will render out the welcome page | ||
router.addRoute("index", State.extend({ | ||
router.routes["index"] = State.extend({ | ||
activate: function () { | ||
this.parent.$outlet.html("Welcome to this application"); | ||
} | ||
})); | ||
}); | ||
// about page | ||
router.addRoute("about", State.extend({ | ||
router.routes["about"] = State.extend({ | ||
activate: function () { | ||
this.parent.$outlet.html("This is about page"); | ||
} | ||
})); | ||
}); | ||
// faq page | ||
router.addRoute("faq", State.extend({ | ||
router.routes["faq"] = State.extend({ | ||
model: function (params) { | ||
@@ -105,5 +105,5 @@ this.params = params; | ||
} | ||
})); | ||
}); | ||
// posts page | ||
router.addRoute("posts.filter", State.extend({ | ||
router.routes["posts.filter"] = State.extend({ | ||
model: function (params) { | ||
@@ -119,3 +119,3 @@ return params; | ||
} | ||
})); | ||
}); | ||
} | ||
@@ -122,0 +122,0 @@ |
@@ -28,3 +28,3 @@ // map(function(match) { | ||
var _ = require("underscore"); | ||
var Promise = require("rsvp").Promise; | ||
var Promise = require("cherrytree/vendor/promise"); | ||
var Router = require("cherrytree"); | ||
@@ -101,3 +101,3 @@ var Route = require("cherrytree/route"); | ||
// first of all, we want an application route | ||
router.addRoute("application", BaseRoute.extend({ | ||
router.routes["application"] = BaseRoute.extend({ | ||
// this is a cherrytree hook for "performing" | ||
@@ -111,12 +111,12 @@ // actions upon entering this route | ||
} | ||
})); | ||
}); | ||
// then we'll create an application.index route that | ||
// will render out the welcome page | ||
router.addRoute("index", BaseRoute.extend({ | ||
router.routes["index"] = BaseRoute.extend({ | ||
templateName: "home" | ||
})); | ||
}); | ||
// blog show | ||
router.addRoute("posts.show", BaseRoute.extend({ | ||
router.routes["posts.show"] = BaseRoute.extend({ | ||
model: function (params) { | ||
@@ -135,6 +135,6 @@ if (!this.sessionStore) { | ||
} | ||
})); | ||
}); | ||
// blog page | ||
router.addRoute("posts.filter", BaseRoute.extend({ | ||
router.routes["posts.filter"] = BaseRoute.extend({ | ||
activate: function (params, queryParams) { | ||
@@ -160,3 +160,3 @@ this.queryParams = queryParams; | ||
} | ||
})); | ||
}); | ||
@@ -163,0 +163,0 @@ // start routing |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
543020
10435
421
Updatedlocation-bar@^2.0.0